├── .gitattributes ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .luaurc ├── .vscode └── settings.json ├── CNAME ├── LICENSE ├── README.md ├── Test ├── Client │ └── Client.client.luau ├── Server │ └── Server.server.luau └── Shared │ ├── ComplexEvent.luau │ ├── ComplexFunction.luau │ ├── EmptyEvent.luau │ ├── EmptyFunction.luau │ ├── ReadyEvent.luau │ ├── SharedEvents.lua │ ├── SimpleEvent.luau │ └── SimpleFunction.luau ├── aftman.toml ├── default.project.json ├── dev.project.json ├── docs ├── .vitepress │ ├── config.ts │ └── theme │ │ ├── custom.css │ │ └── index.ts ├── 2.0 │ ├── Event │ │ ├── Client.md │ │ ├── Server.md │ │ └── index.md │ ├── Function.md │ ├── Red.md │ ├── SharedEvent.md │ └── SharedSignalEvent.md ├── faq.md ├── guide │ ├── events │ │ ├── client.md │ │ ├── declaring.md │ │ ├── server.md │ │ └── sharedevent.md │ ├── functions.md │ └── introduction │ │ ├── getting-started.md │ │ ├── redblox.md │ │ └── what-is-red.md ├── index.md └── public │ ├── favicon.png │ └── logo.png ├── lib ├── Event │ ├── Client.luau │ ├── Server.luau │ └── init.luau ├── Function.luau ├── Identifier.luau ├── Net │ ├── Client.luau │ ├── Server.luau │ └── init.luau ├── SharedEvent.luau └── init.luau ├── package-lock.json ├── package.json └── wally.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.luau linguist-language=Lua -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | pages: write 12 | id-token: write 13 | environment: 14 | name: github-pages 15 | url: ${{ steps.deployment.outputs.page_url }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: 16 23 | cache: npm 24 | - run: npm ci 25 | - name: Build 26 | run: npm run docs:build 27 | - uses: actions/configure-pages@v2 28 | - uses: actions/upload-pages-artifact@v1 29 | with: 30 | path: docs/.vitepress/dist 31 | - name: Deploy 32 | id: deployment 33 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sourcemap.json 2 | node_modules 3 | Packages 4 | docs/.vitepress/dist 5 | docs/.vitepress/cache 6 | wally.lock -------------------------------------------------------------------------------- /.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "luau-lsp.sourcemap.rojoProjectFile": "dev.project.json", 3 | } -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | red.redblox.dev -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 The Redblox Authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Red 3 |
4 | 5 |
6 | 7 | # What is Red? 8 | 9 | Red is a networking library for Roblox that combines blazingly fast performance with a simple API. With Red's tiny size, it is suitable for any project, from tiny experiments to games that serve millions of players. 10 | 11 | ## Features 12 | 13 | ### ⚡Blazingly Fast 14 | 15 | Red achieves high performance by packing events in a compressed format, using up to 75% less bandwidth. Red uses a single remote event with identifiers to reduce bandwidth, and increase performance. 16 | 17 | ### ⚙️No Config 18 | 19 | Red is designed to work out of the box with no configuration required. Just require the library and begin developing. No config means less boilerplate and more time to focus on what matters: your project. 20 | 21 | ### 🔐Type Safety 22 | 23 | Red is written with type safety in mind, so you can use Luau and get full intellisense and linting right in your editor. Full intellisense without any extra work means faster development with autocomplete and catching bugs before they happen. 24 | 25 | ### 🔎Obfuscation 26 | 27 | As part of it's optimization features, Red obfuscates network data to make it harder for cheaters to read networking logs. This makes it significantly harder for cheaters to reverse engineer and cheat in your game. 28 | 29 | ## Documentation 30 | 31 | Red's documentation can be found [here](https://red.redblox.dev). 32 | -------------------------------------------------------------------------------- /Test/Client/Client.client.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | function TestEvents() 4 | local ComplexFunction = require(ReplicatedStorage.ComplexFunction) 5 | local EmptyFunction = require(ReplicatedStorage.EmptyFunction) 6 | local SimpleFunction = require(ReplicatedStorage.SimpleFunction) 7 | 8 | local ComplexEvent = require(ReplicatedStorage.ComplexEvent):Client() 9 | local SimpleEvent = require(ReplicatedStorage.SimpleEvent):Client() 10 | local EmptyEvent = require(ReplicatedStorage.EmptyEvent):Client() 11 | local ReadyEvent = require(ReplicatedStorage.ReadyEvent):Client() 12 | task.wait(0.5) 13 | 14 | print("Registering Listeners") 15 | 16 | ComplexEvent:On(function(Value1, Value2, Value3) 17 | print("ComplexEvent", Value1, Value2, Value3) 18 | end) 19 | 20 | SimpleEvent:On(function(Value) 21 | print("SimpleEvent", Value) 22 | end) 23 | 24 | EmptyEvent:On(function() 25 | print("EmptyEvent") 26 | end) 27 | 28 | task.wait(0.5) 29 | 30 | print("Firing Events") 31 | 32 | ComplexEvent:Fire({ one = { "String Literal", 123 }, two = { 123, "String Literal" } }, "hello world again", 123) 33 | SimpleEvent:Fire(123) 34 | EmptyEvent:Fire() 35 | 36 | task.wait(0.5) 37 | 38 | print("Calling Functions") 39 | 40 | print(ComplexFunction:Call({ one = { "String Literal", 123 }, two = { 123, "String Literal" } }, "hi", 12):Await()) 41 | print(SimpleFunction:Call(123):Await()) 42 | print(EmptyFunction:Call():Await()) 43 | 44 | task.wait(0.5) 45 | 46 | print("Firing Ready") 47 | 48 | ReadyEvent:Fire() 49 | end 50 | 51 | function TestSharedEvents() 52 | print("Testing Shared Events!") 53 | local SharedEvents = require(ReplicatedStorage.SharedEvents) 54 | 55 | task.wait(0.5) 56 | print("Registering Listeners") 57 | 58 | SharedEvents.Empty:SetClientListener(function() 59 | print("Received empty!") 60 | end) 61 | 62 | SharedEvents.Simple:SetClientListener(function(Value) 63 | print("Received simple!", Value) 64 | end) 65 | 66 | SharedEvents.Signal:OnClient(function(Value) 67 | print("Signal 1 called!", Value) 68 | end) 69 | SharedEvents.Signal:OnClient(function(Value) 70 | print("Signal 2 called!", Value) 71 | end) 72 | 73 | SharedEvents.Complex:SetClientListener(function(Value1, Value2, Value3) 74 | print("Received complex!", Value1, Value2, Value3) 75 | end) 76 | 77 | task.wait(0.5) 78 | print("Firing events") 79 | 80 | SharedEvents.Empty:FireServer() 81 | SharedEvents.Simple:FireServer(5) 82 | SharedEvents.Signal:FireServer(6) 83 | SharedEvents.Complex:FireServer( 84 | { one = { "String Literal", 17 }, two = { 123, "String Literal" } }, 85 | "another string", 86 | 5 87 | ) 88 | 89 | task.wait(0.5) 90 | print("Firing ready") 91 | SharedEvents.Ready:FireServer() 92 | end 93 | 94 | TestSharedEvents() 95 | -------------------------------------------------------------------------------- /Test/Server/Server.server.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | function TestEvents() 4 | local ComplexFunction = require(ReplicatedStorage.ComplexFunction) 5 | local SimpleFunction = require(ReplicatedStorage.SimpleFunction) 6 | local EmptyFunction = require(ReplicatedStorage.EmptyFunction) 7 | 8 | local ComplexEvent = require(ReplicatedStorage.ComplexEvent):Server() 9 | local SimpleEvent = require(ReplicatedStorage.SimpleEvent):Server() 10 | local EmptyEvent = require(ReplicatedStorage.EmptyEvent):Server() 11 | local ReadyEvent = require(ReplicatedStorage.ReadyEvent):Server() 12 | 13 | print("Registering Function Callbacks") 14 | 15 | ComplexFunction:SetCallback(function(Player, Value1, Value2, Value3) 16 | print("ComplexFunction", Player, Value1, Value2, Value3) 17 | task.wait(0.2) 18 | 19 | return Value1, Value2, Value3 20 | end) 21 | 22 | SimpleFunction:SetCallback(function(Player, Value) 23 | print("SimpleFunction", Player, Value) 24 | 25 | return tostring(Value) 26 | end) 27 | 28 | EmptyFunction:SetCallback(function(Player) 29 | print("EmptyFunction", Player) 30 | 31 | return 32 | end) 33 | 34 | print("Registering Event Listeners") 35 | 36 | ComplexEvent:On(function(Player, Value1, Value2, Value3) 37 | print("ComplexEvent", Player, Value1, Value2, Value3) 38 | end) 39 | 40 | SimpleEvent:On(function(Player, Value) 41 | print("SimpleEvent", Player, Value) 42 | end) 43 | 44 | EmptyEvent:On(function(Player) 45 | print("EmptyEvent", Player) 46 | end) 47 | 48 | ReadyEvent:On(function(Player) 49 | print(`[{Player.Name}] Ready, firing events!`) 50 | 51 | print(`[{Player.Name}] :Fire`) 52 | SimpleEvent:Fire(Player, 1) 53 | EmptyEvent:Fire(Player) 54 | ComplexEvent:Fire(Player, nil, "hello world again", 1) 55 | 56 | print(`[{Player.Name}] :FireAll`) 57 | SimpleEvent:FireAll(2) 58 | EmptyEvent:FireAll() 59 | ComplexEvent:FireAll( 60 | { one = { "String Literal", 123 }, two = { 123, "String Literal" } }, 61 | "hello world again", 62 | 2 63 | ) 64 | 65 | print(`[{Player.Name}] :FireAllExcept`) 66 | SimpleEvent:FireAllExcept(Player, 3) 67 | EmptyEvent:FireAllExcept(Player) 68 | ComplexEvent:FireAllExcept(Player, nil, "this should be skipped", 3) 69 | 70 | print(`[{Player.Name}] :FireList`) 71 | SimpleEvent:FireList({ Player }, 4) 72 | EmptyEvent:FireList({ Player }) 73 | ComplexEvent:FireList({ Player }, { one = { "String Literal", 8 }, two = { 1, "String Literal" } }, "hi", 4) 74 | 75 | print(`[{Player.Name}] :FireWithFilter`) 76 | SimpleEvent:FireWithFilter(function(OtherPlayer) 77 | return OtherPlayer.Name == Player.Name 78 | end, 5) 79 | EmptyEvent:FireWithFilter(function(OtherPlayer) 80 | return OtherPlayer.Name == Player.Name 81 | end) 82 | ComplexEvent:FireWithFilter(function(OtherPlayer) 83 | return OtherPlayer.Name == Player.Name 84 | end, { one = { "String Literal", 17 }, two = { 123, "String Literal" } }, "another string", 5) 85 | 86 | print(`[{Player.Name}] Done!`) 87 | end) 88 | end 89 | 90 | function TestSharedEvents() 91 | print("Testing Shared Events") 92 | local SharedEvents = require(ReplicatedStorage.SharedEvents) 93 | 94 | print("Registering Event Listeners") 95 | 96 | SharedEvents.Complex:SetServerListener(function(Player, Value1, Value2, Value3) 97 | print("ComplexEvent", Player, Value1, Value2, Value3) 98 | end) 99 | 100 | SharedEvents.Simple:SetServerListener(function(Player, Value) 101 | print("SimpleEvent", Player, Value) 102 | end) 103 | 104 | SharedEvents.Empty:SetServerListener(function(Player) 105 | print("EmptyEvent", Player) 106 | end) 107 | 108 | SharedEvents.Signal:OnServer(function(Player, Value) 109 | print("SignalEvent1", Player, Value) 110 | end) 111 | SharedEvents.Signal:OnServer(function(Player, Value) 112 | print("SignalEvent2", Player, Value) 113 | end) 114 | 115 | SharedEvents.Ready:SetServerListener(function(Player) 116 | print(`[{Player.Name}] Ready, firing events!`) 117 | 118 | print(`[{Player.Name}] :FireClient`) 119 | SharedEvents.Empty:FireClient(Player) 120 | SharedEvents.Simple:FireClient(Player, 1) 121 | SharedEvents.Signal:FireClient(Player, 1) 122 | SharedEvents.Complex:FireClient(Player, nil, "hello world again", 1) 123 | 124 | print(`[{Player.Name}] :FireAllClients`) 125 | SharedEvents.Empty:FireAllClients() 126 | SharedEvents.Simple:FireAllClients(2) 127 | SharedEvents.Signal:FireAllClients(2) 128 | SharedEvents.Complex:FireAllClients( 129 | { one = { "String Literal", 123 }, two = { 123, "String Literal" } }, 130 | "hello world again", 131 | 2 132 | ) 133 | 134 | print(`[{Player.Name}] :FireAllClientsExcept`) 135 | SharedEvents.Empty:FireAllClientsExcept(Player) 136 | SharedEvents.Simple:FireAllClientsExcept(Player, 3) 137 | SharedEvents.Signal:FireAllClientsExcept(Player, 3) 138 | SharedEvents.Complex:FireAllClientsExcept(Player, nil, "this should be skipped", 3) 139 | 140 | print(`[{Player.Name}] :FireClients`) 141 | SharedEvents.Empty:FireClients({ Player }) 142 | SharedEvents.Simple:FireClients({ Player }, 4) 143 | SharedEvents.Signal:FireClients({ Player }, 4) 144 | SharedEvents.Complex:FireClients( 145 | { Player }, 146 | { one = { "String Literal", 8 }, two = { 1, "String Literal" } }, 147 | "hi", 148 | 4 149 | ) 150 | 151 | print(`[{Player.Name}] :FireFilteredClients`) 152 | SharedEvents.Empty:FireFilteredClients(function(OtherPlayer) 153 | return OtherPlayer.Name == Player.Name 154 | end) 155 | SharedEvents.Simple:FireFilteredClients(function(OtherPlayer) 156 | return OtherPlayer.Name == Player.Name 157 | end, 5) 158 | SharedEvents.Signal:FireFilteredClients(function(OtherPlayer) 159 | return OtherPlayer.Name == Player.Name 160 | end, 5) 161 | SharedEvents.Complex:FireFilteredClients(function(OtherPlayer) 162 | return OtherPlayer.Name == Player.Name 163 | end, { one = { "String Literal", 17 }, two = { 123, "String Literal" } }, "another string", 5) 164 | 165 | print(`[{Player.Name}] Done!`) 166 | end) 167 | end 168 | 169 | TestSharedEvents() 170 | -------------------------------------------------------------------------------- /Test/Shared/ComplexEvent.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | local Guard = require(ReplicatedStorage.Packages.Guard) 5 | 6 | local ValueCheck = 7 | Guard.Optional(Guard.Map(Guard.String, Guard.List(Guard.Or(Guard.Literal("String Literal"), Guard.Number)))) 8 | 9 | return Red.Event("ComplexEvent", function(Value1, Value2, Value3) 10 | return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3) 11 | end) 12 | -------------------------------------------------------------------------------- /Test/Shared/ComplexFunction.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | local Guard = require(ReplicatedStorage.Packages.Guard) 5 | 6 | local ValueCheck = 7 | Guard.Optional(Guard.Map(Guard.String, Guard.List(Guard.Or(Guard.Literal("String Literal"), Guard.Number)))) 8 | 9 | return Red.Function("ComplexFunction", function(Value1, Value2, Value3) 10 | return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3) 11 | end, function(Value1, Value2, Value3) 12 | return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3) 13 | end) 14 | -------------------------------------------------------------------------------- /Test/Shared/EmptyEvent.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | 5 | return Red.Event("EmptyEvent", function() 6 | return 7 | end) 8 | -------------------------------------------------------------------------------- /Test/Shared/EmptyFunction.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | 5 | return Red.Function("EmptyFunction", function() 6 | return 7 | end, function() 8 | return 9 | end) 10 | -------------------------------------------------------------------------------- /Test/Shared/ReadyEvent.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | 5 | return Red.Event("ReadyEvent", function() 6 | return 7 | end) 8 | -------------------------------------------------------------------------------- /Test/Shared/SharedEvents.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local Guard = require(ReplicatedStorage.Packages.Guard) 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | 5 | local ValueCheck = 6 | Guard.Optional(Guard.Map(Guard.String, Guard.List(Guard.Or(Guard.Literal("String Literal"), Guard.Number)))) 7 | 8 | return { 9 | Empty = Red.SharedEvent("Empty", function() end), 10 | 11 | Ready = Red.SharedEvent("Ready", function() end), 12 | 13 | Simple = Red.SharedEvent("Simple", function(Number) 14 | return Guard.Number(Number) 15 | end), 16 | 17 | Signal = Red.SharedSignalEvent("Signal", function(Number) 18 | return Guard.Number(Number) 19 | end), 20 | 21 | Complex = Red.SharedEvent("Complex", function(Value1, Value2, Value3) 22 | return ValueCheck(Value1), Guard.String(Value2), Guard.Number(Value3) 23 | end), 24 | } 25 | -------------------------------------------------------------------------------- /Test/Shared/SimpleEvent.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | local Guard = require(ReplicatedStorage.Packages.Guard) 5 | 6 | return Red.Event("SimpleEvent", function(Value) 7 | return Guard.Number(Value) 8 | end) 9 | -------------------------------------------------------------------------------- /Test/Shared/SimpleFunction.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | local Red = require(ReplicatedStorage.Packages.Red) 4 | local Guard = require(ReplicatedStorage.Packages.Guard) 5 | 6 | return Red.Function("SimpleFunction", function(Value) 7 | return Guard.Number(Value) 8 | end, function(Value) 9 | return Guard.String(Value) 10 | end) 11 | -------------------------------------------------------------------------------- /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 | rojo = "rojo-rbx/rojo@7.3.0" 7 | wally = "upliftgames/wally@0.3.2" 8 | # rojo = "rojo-rbx/rojo@6.2.0" -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Red", 3 | "tree": { 4 | "$path": "lib" 5 | } 6 | } -------------------------------------------------------------------------------- /dev.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RedDev", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ServerScriptService": { 6 | "$path": "Test/Server" 7 | }, 8 | "ReplicatedStorage": { 9 | "$path": "Test/Shared", 10 | "Packages": { 11 | "$path": "Packages", 12 | "Red": { 13 | "$path": "lib" 14 | } 15 | } 16 | }, 17 | "StarterPlayer": { 18 | "StarterPlayerScripts": { 19 | "$path": "Test/Client" 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | function nav() { 4 | return [ 5 | { text: 'FAQ', link: '/faq' }, 6 | { text: 'Guide', link: '/guide/introduction/what-is-red' }, 7 | { text: 'API Reference', link: '/2.0/Red' }, 8 | ] 9 | } 10 | 11 | function sidebar() { 12 | return { 13 | '/guide/': [ 14 | { 15 | text: 'Introduction', 16 | items: [ 17 | { text: 'What is Red?', link: '/guide/introduction/what-is-red' }, 18 | { text: 'Getting Started', link: '/guide/introduction/getting-started' }, 19 | { text: 'Redblox', link: '/guide/introduction/redblox' }, 20 | ] 21 | }, 22 | { 23 | text: 'Events', 24 | items: [ 25 | { text: 'Declaring Events', link: '/guide/events/declaring' }, 26 | { text: 'Server Usage', link: '/guide/events/server' }, 27 | { text: 'Client Usage', link: '/guide/events/client' }, 28 | { text: 'Shared Events', link: '/guide/events/sharedevent' } 29 | ] 30 | }, 31 | { 32 | text: 'Functions', 33 | link: '/guide/functions', 34 | }, 35 | ], 36 | 37 | '/2.0/': [ 38 | { 39 | text: 'API Reference', 40 | items: [ 41 | { text: 'Red', link: '/2.0/Red' }, 42 | { 43 | text: 'Event', 44 | link: '/2.0/Event', 45 | items: [ 46 | { text: 'Server', link: '/2.0/Event/Server' }, 47 | { text: 'Client', link: '/2.0/Event/Client' }, 48 | ] 49 | }, 50 | { text: 'Function', link: '/2.0/Function' }, 51 | { text: 'SharedEvent', link: '/2.0/SharedEvent' }, 52 | { text: 'SharedSignalEvent', link: '/2.0/SharedSignalEvent' } 53 | ], 54 | } 55 | ], 56 | } 57 | } 58 | 59 | export default defineConfig({ 60 | title: 'Red', 61 | description: 'A simple, fast, and powerful networking library.', 62 | lang: 'en-US', 63 | head: [ 64 | ['link', { rel: 'icon', href: '/favicon.png' }], 65 | ], 66 | 67 | themeConfig: { 68 | logo: '/logo.png', 69 | siteTitle: false, 70 | outline: 'deep', 71 | 72 | socialLinks: [ 73 | { icon: 'github', link: 'https://github.com/red-blox/red' }, 74 | { icon: 'discord', link: 'https://discord.gg/mchCdAFPWU' }, 75 | ], 76 | 77 | search: { 78 | provider: 'algolia', 79 | options: { 80 | appId: 'OXUAMIFLZV', 81 | apiKey: 'b30ea2870076353706bb93154da80143', 82 | indexName: 'red', 83 | 84 | } 85 | }, 86 | 87 | nav: nav(), 88 | sidebar: sidebar(), 89 | }, 90 | }) 91 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand-1: var(--vp-c-red-1); 3 | --vp-c-brand-2: var(--vp-c-red-2); 4 | --vp-c-brand-3: var(--vp-c-red-3); 5 | --vp-c-brand-soft: var(--vp-c-red-soft); 6 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme"; 2 | import './custom.css'; 3 | 4 | export default DefaultTheme; -------------------------------------------------------------------------------- /docs/2.0/Event/Client.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | The client event object. 4 | 5 | ## Fire 6 | 7 | Fires the event to the server. 8 | 9 | ```lua 10 | ( 11 | ...: T..., -- The event payload 12 | ) -> () 13 | ``` 14 | 15 | This method fires the event to the server with the payload. 16 | 17 | ```lua 18 | MyEvent:Fire("Hello, World!") 19 | ``` 20 | 21 | ## On 22 | 23 | Sets the event's listener. 24 | 25 | ```lua 26 | ( 27 | Listener: (T...) -> (), -- The listener 28 | ) -> () 29 | ``` 30 | 31 | This method sets the event's listener. 32 | 33 | ```lua 34 | MyEvent:On(function(...) 35 | print(...) 36 | end) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/2.0/Event/Server.md: -------------------------------------------------------------------------------- 1 | # Server 2 | 3 | The server event object. 4 | 5 | ## Fire 6 | 7 | Fires the event to a single client. 8 | 9 | ```lua 10 | ( 11 | Player: Player, -- The client to fire to 12 | ...: T..., -- The event payload 13 | ) -> () 14 | ``` 15 | 16 | This method fires the event to a single client. 17 | 18 | ```lua 19 | MyEvent:Fire(player, "Hello, World!") 20 | ``` 21 | 22 | ## FireAll 23 | 24 | Fires the event to all clients. 25 | 26 | ```lua 27 | ( 28 | ...: T..., -- The event payload 29 | ) -> () 30 | ``` 31 | 32 | This method fires the event to all clients. 33 | 34 | ```lua 35 | MyEvent:FireAll("Hello, World!") 36 | ``` 37 | 38 | ## FireAllExcept 39 | 40 | Fires the event to all clients except one. 41 | 42 | ```lua 43 | ( 44 | Player: Player, -- The client to exclude 45 | ...: T..., -- The event payload 46 | ) -> () 47 | ``` 48 | 49 | This method fires the event to all clients except one. 50 | 51 | ```lua 52 | MyEvent:FireAllExcept(player, "Hello, World!") 53 | ``` 54 | 55 | ## FireList 56 | 57 | Fires the event to a list of clients. 58 | 59 | ```lua 60 | ( 61 | Players: { Player }, -- The clients to fire to 62 | ...: T..., -- The event payload 63 | ) -> () 64 | ``` 65 | 66 | This method fires the event to a list of clients. 67 | 68 | ```lua 69 | MyEvent:FireList({ player }, "Hello, World!") 70 | ``` 71 | 72 | ## FireWithFilter 73 | 74 | Fires the event to all clients that pass the filter. 75 | 76 | ```lua 77 | ( 78 | Filter: (Player) -> boolean, -- The filter function 79 | ...: T..., -- The event payload 80 | ) -> () 81 | ``` 82 | 83 | This method fires the event to all clients that pass the filter. 84 | 85 | ```lua 86 | MyEvent:FireWithFilter(function(player) 87 | return player.Name == "Player1" 88 | end, "Hello, World!") 89 | ``` 90 | 91 | ## On 92 | 93 | Sets the event's listener. 94 | 95 | ```lua 96 | ( 97 | Listener: (T...) -> (), -- The function to connect 98 | ) -> () 99 | ``` 100 | 101 | This method sets the event's listener. 102 | 103 | ```lua 104 | MyEvent:On(function(...) 105 | print(...) 106 | end) 107 | ``` 108 | 109 | ::: danger 110 | You cannot set more than one listener for an event. Attempting to do so will result in an error. 111 | ::: 112 | -------------------------------------------------------------------------------- /docs/2.0/Event/index.md: -------------------------------------------------------------------------------- 1 | # Event 2 | 3 | Event objects are used to get [server](./Server) and [client](./Client) event objects. They do not do any networking themselves. 4 | 5 | ## Server 6 | 7 | Creates and returns a [server event](./Server) object. 8 | 9 | ```lua 10 | () -> Server 11 | ``` 12 | 13 | ## Client 14 | 15 | Creates and returns a [client event](./Client) object. 16 | 17 | ```lua 18 | () -> Client 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/2.0/Function.md: -------------------------------------------------------------------------------- 1 | # Function 2 | 3 | Functions allow RemoteFunction-like behavior in Red for calling client -> server. 4 | 5 | ## SetCallback 6 | 7 | Sets the function's callback. 8 | 9 | ```lua 10 | ( 11 | Callback: (Player, A...) -> R... -- The callback to set. 12 | ) -> () 13 | ``` 14 | 15 | A singular callback must be set on the server to allow clients to call the function. This callback is given the arguments and must return the expected return values. This callback may only be set on the server, attempting to set it on the client will result in an error. 16 | 17 | ```lua 18 | local Function = require(Path.To.Function) 19 | 20 | Function:SetCallback(function(Player, Arg1, Arg2, Arg3) 21 | return Arg1, Arg2, Arg3 22 | end) 23 | ``` 24 | 25 | ## Call 26 | 27 | Calls the function on the server. 28 | 29 | ```lua 30 | ( 31 | ...A: any -- The arguments to pass to the function. 32 | ) -> Future -- A success boolean followed by the returned values 33 | ``` 34 | 35 | Functions can only be called from the client. The client must pass valid arguments to the function, and will be given back a [Future](https://util.redblox.dev/future) that completes with the returned values. 36 | 37 | It also returns a success boolean, similar to pcall, which is false if the server's callback function errored, and true if it was successful. 38 | 39 | ```lua 40 | local Function = require(Path.To.Function) 41 | 42 | local Success, Ret1, Ret2, Ret3 = Function:Call(Arg1, Arg2, Arg3):Await() 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/2.0/Red.md: -------------------------------------------------------------------------------- 1 | # Red 2 | 3 | The root of the Red library. 4 | 5 | ## Event 6 | 7 | Creates a new [event](./Event/index) object. 8 | 9 | ```lua 10 | ( 11 | Name: string | { Name: string, Unreliable: boolean? }, -- Event config 12 | Validate: (...unknown) -> T..., -- Validates event payloads 13 | ) -> Event 14 | ``` 15 | 16 | This will create an event with the passed config. 17 | 18 | If you pass a string, it will be used as the event name and the event will be reliable. If you pass a table you have the option to make the event unreliable. 19 | 20 | ::: danger 21 | The name of the event must be unique, using the same name twice will result in an error. 22 | ::: 23 | 24 | The validation function is used to validate the type of the payloads. The function has three rules: 25 | 26 | 1. The callback returns the arguments in the same order they were passed in. 27 | 2. The callback must error if the arguments are invalid. 28 | 3. The callback must narrow the types of the arguments. 29 | 30 | ```lua 31 | return Red.Event("EventName", function(Number, String) 32 | return Guard.Number(Number), Guard.String(String) 33 | end) 34 | ``` 35 | 36 | ::: tip 37 | It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck payloads as it also narrows types. 38 | ::: 39 | 40 | ## Function 41 | 42 | Creates a new [function](./Function) object. 43 | 44 | ```lua 45 | ( 46 | Name: string, -- The name of the function 47 | ValidateArg: (...unknown) -> A..., -- Validates function arguments 48 | ValidateRet: (...unknown) -> R..., -- Validates function returns 49 | ) -> Function 50 | ``` 51 | 52 | This will create a function with the passed name. 53 | 54 | ::: danger 55 | The name of the function must be unique, using the same name twice will result in an error. 56 | ::: 57 | 58 | The validation functions are used to validate the type of the arguments and returns. The functions have the same three rules as event validation functions: 59 | 60 | 1. The callback returns the arguments in the same order they were passed in. 61 | 2. The callback must error if the arguments are invalid. 62 | 3. The callback must narrow the types of the arguments. 63 | 64 | ```lua 65 | return Red.Function("FunctionName", function(Number, String) 66 | return Guard.Number(Number), Guard.String(String) 67 | end, function(Number, String) 68 | return Guard.Number(Number), Guard.String(String) 69 | end) 70 | ``` 71 | 72 | ::: tip 73 | It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck as it also narrows types. 74 | ::: 75 | 76 | ## SharedEvent 77 | Creates a new [SharedEvent](./SharedEvent) object. 78 | ```lua 79 | ( 80 | Name: string | { Name: string, Unreliable: boolean? }, -- Event config 81 | Validate: (...unknown) -> T... -- Validates event payloads 82 | ) -> SharedCallEvent 83 | ``` 84 | This will create a SharedEvent with the passed config. 85 | 86 | If you pass a string, it will be used as the event name and the event will be reliable. If you pass a table you have the option to make the event unreliable. 87 | 88 | ::: danger 89 | The name of the event must be unique, using the same name twice will result in an error. 90 | ::: 91 | 92 | The validation function is used to validate the type of the payloads. The function has three rules: 93 | 94 | 1. The callback returns the arguments in the same order they were passed in. 95 | 2. The callback must error if the arguments are invalid. 96 | 3. The callback must narrow the types of the arguments. 97 | 98 | ```lua 99 | return Red.SharedEvent("EventName", function(Number, String) 100 | return Guard.Number(Number), Guard.String(String) 101 | end) 102 | ``` 103 | ::: tip 104 | It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck as it also narrows types. 105 | ::: 106 | 107 | ## SharedSignalEvent 108 | Creates a new [SharedSignalEvent](./SharedSignalEvent) object. 109 | ```lua 110 | ( 111 | Name: string | { Name: string, Unreliable: boolean? }, -- Event config 112 | Validate: (...unknown) -> T... -- Validates event payloads 113 | ) -> SharedSignalEvent 114 | ``` 115 | This will create a SharedSignalEvent with the passed config. 116 | 117 | If you pass a string, it will be used as the event name and the event will be reliable. If you pass a table you have the option to make the event unreliable. 118 | 119 | ::: danger 120 | The name of the event must be unique, using the same name twice will result in an error. 121 | ::: 122 | 123 | The validation function is used to validate the type of the payloads. The function has three rules: 124 | 125 | 1. The callback returns the arguments in the same order they were passed in. 126 | 2. The callback must error if the arguments are invalid. 127 | 3. The callback must narrow the types of the arguments. 128 | 129 | ```lua 130 | return Red.SharedSignalEvent("EventName", function(Number, String) 131 | return Guard.Number(Number), Guard.String(String) 132 | end) 133 | ``` 134 | ::: tip 135 | It is recommended to use [Guard](https://util.redblox.dev/guard) to typecheck as it also narrows types. 136 | ::: -------------------------------------------------------------------------------- /docs/2.0/SharedEvent.md: -------------------------------------------------------------------------------- 1 | # SharedEvent 2 | SharedEvents are built on the same backend networking as regular events. However, they do not require an explicit `:Server` or `:Client` call to use. 3 | 4 | Unlike [SharedSignalEvents](SharedSignalEvent), only one listener may be set at any time. Setting a new listener will overwrite the previous one, if it existed. 5 | 6 | ## SetServerListener 7 | Sets the server callback. 8 | ```lua 9 | ( 10 | Listener: (Player: Player, T...) -> () -- The listener to register 11 | ) -> () 12 | ``` 13 | This method sets the server listener. Errors if called from the client. 14 | ```lua 15 | MyEvent:SetServerListener(function(Player, Value) 16 | print(Player, Value) 17 | end) 18 | ``` 19 | 20 | ## SetClientListener 21 | Sets the client callback. 22 | ```lua 23 | ( 24 | Listener: (T...) -> () -- The listener to register 25 | ) -> () 26 | ``` 27 | This method sets the client listener. Errors if called from the server. 28 | ```lua 29 | MyEvent:SetClientListener(function(Value) 30 | print(Value) 31 | end) 32 | ``` 33 | 34 | ## FireServer 35 | Fires the event to the server. 36 | ```lua 37 | ( 38 | ...: T... -- The event payload 39 | ) -> () 40 | ``` 41 | This method fires the event to the server. Errors if called from the server. 42 | ```lua 43 | MyEvent:FireServer("Hello, World!") 44 | ``` 45 | 46 | ## FireClient 47 | Fires the event to a specific player. 48 | ```lua 49 | ( 50 | Player: Player, -- The target player 51 | ...: T... -- The event payload 52 | ) -> () 53 | ``` 54 | This method fires the event to a specific client. Errors if called from the client. 55 | ```lua 56 | MyEvent:FireClient(player, "Hello, World!") 57 | ``` 58 | 59 | ## FireAllClients 60 | Fires the event to all players. 61 | ```lua 62 | ( 63 | ...: T... -- The event payload 64 | ) -> () 65 | ``` 66 | This method fires the event to all players. Errors if called from the client. 67 | ```lua 68 | MyEvent:FireAllClients("Hello, World!") 69 | ``` 70 | 71 | ## FireAllClientsExcept 72 | Fires the event to every player **except** one. 73 | ```lua 74 | ( 75 | Player: Player, -- Player to be ignored 76 | ...: T... -- The event payload 77 | ) 78 | ``` 79 | This method fires the event to every player except one. Errors if called from the client. 80 | ```lua 81 | MyEvent:FireAllClientsExcept(player, "Hello, World!") 82 | ``` 83 | 84 | ## FireClients 85 | Fires the event to every player in the given list. 86 | ```lua 87 | ( 88 | Players: { Player }, -- List of players to fire the event to 89 | ...: T... -- The event payload 90 | ) -> () 91 | ``` 92 | This method fires the event to every player in the list. Errors if called from the client. 93 | ```lua 94 | MyServer:FireClients({ player }, "Hello, World!") 95 | ``` 96 | 97 | ## FireFilteredClients 98 | Fires the event to every player that passes the given filter function. 99 | ```lua 100 | ( 101 | Filter: (Player) -> boolean -- Must return true for a player to receive the event. 102 | ...: T... -- The event payload 103 | ) -> () 104 | ``` 105 | This method fires the event to every player that passes the filter. Errors if called from the client. 106 | ```lua 107 | MyServer:FireFilteredClients(function(player) 108 | return player.Name == "Player1" 109 | end, "Hello, World!") 110 | ``` -------------------------------------------------------------------------------- /docs/2.0/SharedSignalEvent.md: -------------------------------------------------------------------------------- 1 | # SharedSignalEvent 2 | SharedEvents are built on the same backend networking as regular events. However, they do not require an explicit `:Server` or `:Client` call to use. 3 | 4 | Unlike [SharedEvents](SharedEvent), SharedSignalEvents may have any number of listeners set. 5 | 6 | ## OnServer 7 | Adds a server callback, returning a disconnect function. 8 | ```lua 9 | ( 10 | Listener: (Player: Player, T...) -> () -- The listener to register 11 | ) -> (() -> ()) -- Disconnect function 12 | ``` 13 | This method adds a server callback, returning a disconnect function. Errors if called from the client. 14 | ```lua 15 | MyEvent:OnServer(function(Player, Argument) 16 | print(Player, Argument) 17 | end) 18 | ``` 19 | 20 | ## OnClient 21 | Adds a client callback, returning a disconnect function. 22 | ```lua 23 | ( 24 | Listener: (T...) -> () -- The listener to register 25 | ) -> (() -> ()) -- Disconnect function 26 | ``` 27 | This method adds a client callback, returning a disconnect function. Errors if called from the server. 28 | ```lua 29 | MyEvent:OnClient(function(Argument) 30 | print(Argument) 31 | end) 32 | ``` 33 | 34 | ## FireServer 35 | Fires the event to the server. 36 | ```lua 37 | ( 38 | ...: T... -- The event payload 39 | ) -> () 40 | ``` 41 | This method fires the event to the server. Errors if called from the server. 42 | ```lua 43 | MyEvent:FireServer("Hello, World!") 44 | ``` 45 | 46 | ## FireClient 47 | Fires the event to a specific player. 48 | ```lua 49 | ( 50 | Player: Player, -- The target player 51 | ...: T... -- The event payload 52 | ) -> () 53 | ``` 54 | This method fires the event to a specific client. Errors if called from the client. 55 | ```lua 56 | MyEvent:FireClient(player, "Hello, World!") 57 | ``` 58 | 59 | ## FireAllClients 60 | Fires the event to all players. 61 | ```lua 62 | ( 63 | ...: T... -- The event payload 64 | ) -> () 65 | ``` 66 | This method fires the event to all players. Errors if called from the client. 67 | ```lua 68 | MyEvent:FireAllClients("Hello, World!") 69 | ``` 70 | 71 | ## FireAllClientsExcept 72 | Fires the event to every player **except** one. 73 | ```lua 74 | ( 75 | Player: Player, -- Player to be ignored 76 | ...: T... -- The event payload 77 | ) 78 | ``` 79 | This method fires the event to every player except one. Errors if called from the client. 80 | ```lua 81 | MyEvent:FireAllClientsExcept(player, "Hello, World!") 82 | ``` 83 | 84 | ## FireClients 85 | Fires the event to every player in the given list. 86 | ```lua 87 | ( 88 | Players: { Player }, -- List of players to fire the event to 89 | ...: T... -- The event payload 90 | ) -> () 91 | ``` 92 | This method fires the event to every player in the list. Errors if called from the client. 93 | ```lua 94 | MyServer:FireClients({ player }, "Hello, World!") 95 | ``` 96 | 97 | ## FireFilteredClients 98 | Fires the event to every player that passes the given filter function. 99 | ```lua 100 | ( 101 | Filter: (Player) -> boolean -- Must return true for a player to receive the event. 102 | ...: T... -- The event payload 103 | ) -> () 104 | ``` 105 | This method fires the event to every player that passes the filter. Errors if called from the client. 106 | ```lua 107 | MyServer:FireFilteredClients(function(player) 108 | return player.Name == "Player1" 109 | end, "Hello, World!") 110 | ``` -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How can I contribute to or support Red? 4 | 5 | Red is open to contributions - please open an issue or comment before beginning work on a feature or bugfix so that work isn't duplicated and so that we can discuss the implementation. 6 | 7 | If you'd like to support me and my development of Red, you can [buy me a coffee](https://www.buymeacoffee.com/jack.ink). 8 | 9 | ## Reliability and Ordering? 10 | 11 | Events in Red are built on top of RemoteEvents, and as such are fully reliable. 12 | 13 | However, events are not fully ordered. 14 | 15 | - If you fire two different events in the same frame, they are unordered. 16 | - If you fire the same event twice in the same frame, they are ordered. 17 | - If you fire any events in different frames, they are ordered. 18 | 19 | ## Ratelimiting? 20 | 21 | Red has no built-in rate limiting; however, there is a [ratelimiting utility](https://util.redblox.dev/ratelimit) available. 22 | 23 | ```lua 24 | local Ratelimit = require(Path.To.Ratelimit) 25 | local MyEvent = require(Path.To.MyEvent) 26 | 27 | local MyEventRatelimit = Ratelimit(10, 1) 28 | MyEvent:On(function(Player) 29 | if MyEventRatelimit(Player) then 30 | print("Fired!") 31 | else 32 | print("Ratelimited!") 33 | end 34 | end) 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/guide/events/client.md: -------------------------------------------------------------------------------- 1 | # Client Usage 2 | 3 | Just like on the server, we must retrieve the event object before we can do anything else. 4 | 5 | ```lua 6 | local MyEvent = require(ReplicatedStorage.Events.MyEvent):Client() 7 | ``` 8 | 9 | We'll also use the same event module as on the server. 10 | 11 | ```lua 12 | local Guard = require(Path.To.Guard) 13 | local Red = require(Path.To.Red) 14 | 15 | return Red.Event("MyEvent", function(Number, String, Boolean) 16 | return Guard.Number(Number), Guard.String(String), Guard.Boolean(Boolean) 17 | end) 18 | ``` 19 | 20 | ## Firing Events 21 | 22 | The client exposes only one method to fire events to the server. 23 | 24 | ```lua 25 | MyEvent:Fire(1, "Hello", true) 26 | ``` 27 | 28 | This method accepts payload(s) and fires them to the server. The server will receive the payload(s) in the same order they were passed to `:Fire`. 29 | 30 | ## Listening to Events 31 | 32 | As with the server, the client can only have one listener for each event. 33 | 34 | ```lua 35 | MyEvent:On(function(Number, String, Boolean) 36 | print(Number, String, Boolean) 37 | end) 38 | ``` 39 | 40 | Listeners are passed the payload(s) sent by the server. 41 | -------------------------------------------------------------------------------- /docs/guide/events/declaring.md: -------------------------------------------------------------------------------- 1 | # Declaring Events 2 | 3 | Events in Red are declared in shared modules. Each one of these modules contains one event. Declaring events in this way enables strict typing with luau for the event's payload. 4 | 5 | Create a module in ReplicatedStorage. Inside the module import Red, and return a new event. 6 | 7 | ```lua 8 | local Red = require(Path.To.Red) 9 | 10 | return Red.Event("MostBasicEvent", function() 11 | return 12 | end) 13 | ``` 14 | 15 | This is the most basic event in Red. Here we've declared an event which has no payload. The event name is passed as the first argument to `Red.Event`. 16 | 17 | ::: danger 18 | Make sure the event name is unique - using the same name multiple times will result in an error. 19 | ::: 20 | 21 | ## Event Payloads 22 | 23 | We now have an event - but we can't send any data with it. To send a payload with our event we must modify the callback we passed to `Red.Event`. 24 | 25 | This callback is a validation check callback. This callback's purpose is to assert the types of the payload. 26 | 27 | ```lua 28 | return Red.Event("EventWithPayload", function(Value) 29 | assert(type(Value) == "number") 30 | 31 | return Value 32 | end) 33 | ``` 34 | 35 | This callback has a few rules: 36 | 37 | 1. The callback returns the arguments in the same order they were passed in. 38 | 2. The callback must error if the arguments are invalid. 39 | 3. The callback must narrow the types of the arguments. 40 | 41 | ::: info 42 | This callback is only called when a client fires the event to the server. Do not use this callback as middleware, logging, or other side effects. 43 | ::: 44 | 45 | Following the first two rules is fairly simple, however the third rule is a bit more complex - yet still necessary. Narrowing and checking simple types is very easy (like in the example above), but when it comes to complex types it can be more difficult. 46 | 47 | You can use a library like [Guard](https://util.redblox.dev/guard) to both narrow and check types at the same time. 48 | 49 | ```lua 50 | local Guard = require(Path.To.Guard) 51 | 52 | local Check = Guard.Map(Guard.String, Guard.List(Guard.Number)) 53 | 54 | return Red.Event("EventUsingGuard", function(Value) 55 | return Check(Value) 56 | end) 57 | ``` 58 | 59 | Guard will both check your types by erroring if they don't match, and narrow the types by returning the value. This is the recommended way to check and narrow types. 60 | 61 | ## Multiple Payloads 62 | 63 | Events can have multiple payloads expressed as a tuple. The following event is for a squad add and remove system. 64 | 65 | ```lua 66 | local ActionCheck = Guard.Or(Guard.Literal("Add"), Guard.Literal("Remove")) 67 | local PlayerCheck = function(Value: unknown) 68 | assert(typeof(Value) == "Instance") 69 | assert(Value:IsA("Player")) 70 | 71 | return Value 72 | end 73 | 74 | return Red.Event("EventWithMultiplePayloads", function(Action, Player) 75 | return ActionCheck(Action), PlayerCheck(Player) 76 | end) 77 | ``` 78 | 79 | ::: warning 80 | Ensure you follow rule 1 when using multiple payloads. The callback must return the arguments in the same order they were passed in. 81 | ::: 82 | 83 | ## Unreliable Events 84 | 85 | Events can be unreliable. This means that the event will not be guaranteed to fire. This is useful for events that are not important, or are fired very frequently. 86 | 87 | To make an event unreliable, pass a table as the first argument to `Red.Event`. 88 | 89 | ```lua 90 | return Red.Event({ 91 | Name = "UnreliableEvent", 92 | Unreliable = true, 93 | }, function() 94 | return 95 | end) 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/guide/events/server.md: -------------------------------------------------------------------------------- 1 | # Server Usage 2 | 3 | Before we do anything else we must retrieve the event, specifically the server event object. 4 | 5 | ```lua 6 | local MyEvent = require(ReplicatedStorage.Events.MyEvent):Server() 7 | ``` 8 | 9 | Note that we required the event module and called `:Server()` on the event object. At this point we have the server event object and can use it to fire and listen to the event. 10 | 11 | ## Event Module 12 | 13 | For simplicity we'll be using the same event module throughout this guide. 14 | 15 | ```lua 16 | local Guard = require(Path.To.Guard) 17 | local Red = require(Path.To.Red) 18 | 19 | return Red.Event("MyEvent", function(Number, String, Boolean) 20 | return Guard.Number(Number), Guard.String(String), Guard.Boolean(Boolean) 21 | end) 22 | ``` 23 | 24 | This event has three payloads, a number, string, and boolean. The callback will error if the types are invalid, and return the values if they are valid. 25 | 26 | ## Firing Events 27 | 28 | In constrast to the client, the server exposes many methods to fire events. 29 | 30 | ### Fire 31 | 32 | ```lua 33 | MyEvent:Fire(Player, 1, "Hello", true) 34 | ``` 35 | 36 | This method fires an event to player passed as the first argument, and any payload passed after. 37 | 38 | ### FireAll 39 | 40 | ```lua 41 | MyEvent:FireAll(1, "Hello", true) 42 | ``` 43 | 44 | This will fire the event and payload to all players, and as such does not have a player argument - only payload. 45 | 46 | ### FireAllExcept 47 | 48 | ```lua 49 | MyEvent:FireAllExcept(Player, 1, "Hello", true) 50 | ``` 51 | 52 | This is the exact same as `FireAll`, _except_ that it does not fire to the player passed as the first argument. 53 | 54 | ### FireList 55 | 56 | ```lua 57 | MyEvent:FireList({ Player }, 1, "Hello", true) 58 | ``` 59 | 60 | This method accepts a list of players as the first argument, and fires the event to all of them. The payload is passed after the player list. 61 | 62 | ### FireWithFilter 63 | 64 | ```lua 65 | MyEvent:FireWithFilter(function(Player) 66 | return Player.Team.Name == "Red" 67 | end, 1, "Hello", true) 68 | ``` 69 | 70 | This method accepts a filter function as the first argument, and fires the event to all players where the filter function returns true. The payload is passed after the filter function. 71 | 72 | ## Listening to Events 73 | 74 | Listening to events in Red is different than what is common. In most libraries you can connect **many** listeners to an event, however in Red you can only set a **single** listener to each event. Attempting to set multiple listeners will result in an error. 75 | 76 | ```lua 77 | MyEvent:On(function(Player, Number, String, Boolean) 78 | print(Player, Number, String, Boolean) 79 | end) 80 | ``` 81 | 82 | Listeners are set using the `On` method. They are passed the player who fired the event, and any payload passed after. Before the listener is called the validation callback is called to check the validity of the payload. 83 | -------------------------------------------------------------------------------- /docs/guide/events/sharedevent.md: -------------------------------------------------------------------------------- 1 | # Shared Events 2 | Shared Events are simply a different way of using Events. They both use the same Red backend. 3 | 4 | They have the major benefit of not needing to explicitly call `Event:Server` or `Event:Client` before being usable. Both your server and client code can simply require them and start using them. 5 | 6 | There are two types of Shared Events: The default [SharedEvent](/2.0/SharedEvent), and the Signal-based [SharedSignalEvent](/2.0/SharedSignalEvent). By default, Shared Events only allow you to set one callback function, but Shared Signal Events allow you to have any number of callbacks. 7 | 8 | This is marginally worse for performance, as it has to use a Signal to fire the callbacks - hence why it is not the default. 9 | 10 | ::: warning 11 | Setting the listener of a regular Shared Event twice will silently overwrite the first callback. 12 | ::: 13 | 14 | ## Declaring Shared Events 15 | Shared Events are declared in a similar way to regular Events. 16 | 17 | ```lua 18 | local Red = require(Path.To.Red) 19 | 20 | return Red.SharedEvent("UniqueEventName", function() 21 | return 22 | end) 23 | ``` 24 | 25 | However they also allow for another pattern, which is to declare multiple events in one file. This is possible with regular events, but it is finnicky as you still need to call `:Client` and `:Server`. It is much easier with Shared Events. 26 | 27 | ```lua 28 | local Red = require(Path.To.Red) 29 | local Guard = require(Path.To.Guard) 30 | 31 | return { 32 | BasicEvent = Red.SharedEvent("BasicEvent", function() 33 | return 34 | end), 35 | 36 | BasicSignalEvent = Red.SharedSignalEvent("BasicSignalEvent", function() 37 | return 38 | end), 39 | 40 | PayloadEvent = Red.SharedEvent("PayloadEvent", function(number) 41 | return Guard.Number(number) 42 | end), 43 | 44 | PayloadSignalEvent = Red.SharedSignalEvent("PayloadSignalEvent", function(number) 45 | return Guard.Number(number) 46 | end), 47 | 48 | UnreliableEvent = Red.SharedEvent({ 49 | Name = "UnreliableEvent", 50 | Unreliable = true 51 | }, function(number) 52 | return Guard.Number(number) 53 | end) 54 | } 55 | ``` 56 | 57 | ## Listening to events 58 | You use a different function to register listeners, depending on if it's a Signal Shared Event or not, as well as whether you're on the server or the client. 59 | 60 | ### Client 61 | ```lua 62 | -- Assuming the code snippet above is our events file 63 | local Events = require(Path.To.Events) 64 | 65 | -- Shared Event 66 | Events.PayloadEvent:SetClientListener(function(number) 67 | print("Client Received!", number) 68 | end) 69 | 70 | -- Shared Signal Event 71 | -- You can have as many listeners as you want 72 | Events.PayloadSignalEvent:OnClient(function(number) 73 | print("Callback 1", number) 74 | end) 75 | 76 | local DisconnectFunction = Events.PayloadSignalEvent:OnClient(function(number) 77 | print("Callback 2", number) 78 | end) 79 | 80 | DisconnectFunction() -- Disconnects the 2nd callback. 81 | ``` 82 | 83 | ### Server 84 | ```lua 85 | -- Assuming the code snippet above is our events file 86 | local Events = require(Path.To.Events) 87 | 88 | -- Shared Event 89 | Events.PayloadEvent:SetServerListener(function(Player, number) 90 | print("Server received from", Player, number) 91 | end) 92 | 93 | -- Shared Signal Event 94 | -- You can have as many listeners as you want 95 | Events.PayloadSignalEvent:OnServer(function(Player, number) 96 | print("Callback 1 from", Player, number) 97 | end) 98 | 99 | local DisconnectFunction = Events.PayloadSignalEvent:OnServer(function(Player, number) 100 | print("Callback 2 from", Player, number) 101 | end) 102 | 103 | DisconnectFunction() -- Disconnects the 2nd callback. 104 | ``` 105 | 106 | ## Firing Events 107 | Like regular Events, Shared Events offer a lot of different firing functions. 108 | 109 | These are identical for both types of Shared Event. 110 | 111 | ### FireServer 112 | This method fires the event to the server. Errors if called from the server. 113 | ```lua 114 | MyEvent:FireServer("Hello, World!") 115 | ``` 116 | 117 | ### FireClient 118 | This method fires the event to a specific client. Errors if called from the client. 119 | ```lua 120 | MyEvent:FireClient(player, "Hello, World!") 121 | ``` 122 | 123 | ### FireAllClients 124 | This method fires the event to all players. Errors if called from the client. 125 | ```lua 126 | MyEvent:FireAllClients("Hello, World!") 127 | ``` 128 | 129 | ### FireAllClientsExcept 130 | This method fires the event to every player except one. Errors if called from the client. 131 | ```lua 132 | MyEvent:FireAllClientsExcept(player, "Hello, World!") 133 | ``` 134 | 135 | ### FireClients 136 | This method fires the event to every player in the list. Errors if called from the client. 137 | ```lua 138 | MyServer:FireClients({ player }, "Hello, World!") 139 | ``` 140 | 141 | ### FireFilteredClients 142 | This method fires the event to every player that passes the filter. Errors if called from the client. 143 | ```lua 144 | MyServer:FireFilteredClients(function(player) 145 | return player.Name == "Player1" 146 | end, "Hello, World!") 147 | ``` -------------------------------------------------------------------------------- /docs/guide/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Functions are Red's version of RemoteFunctions. If you don't know what they are, it's a way to call code on the server from the client and get return values. Red only allows clients to call the server, not the server to call clients. 4 | 5 | ## Declaring 6 | 7 | Functions are declared similarly to events, they take two validation callbacks: one for the arguments and one for the return value. 8 | 9 | ```lua 10 | local Red = require(Path.To.Red) 11 | 12 | return Red.Function("FunctionName", function(Arg1, Arg2, Arg3) 13 | assert(type(Arg1) == "string") 14 | assert(type(Arg2) == "number") 15 | assert(type(Arg3) == "boolean") 16 | 17 | return Arg1, Arg2, Arg3 18 | end, function(Ret1, Ret2, Ret3) 19 | assert(type(Ret1) == "string") 20 | assert(type(Ret2) == "number") 21 | assert(type(Ret3) == "boolean") 22 | 23 | return Ret1, Ret2, Ret3 24 | end) 25 | ``` 26 | 27 | These callbacks must follow the same three rules that event validation callbacks do: 28 | 29 | 1. The callback returns the arguments in the same order they were passed in. 30 | 2. The callback must error if the arguments are invalid. 31 | 3. The callback must narrow the types of the arguments. 32 | 33 | These callbacks are only called in specific circumstances. Do not use these callbacks as middleware, logging, or other side effects. 34 | 35 | ::: tip 36 | I once again suggest using [Guard](https://util.redblox.dev/guard) to both narrow and check types at the same time. 37 | 38 | ```lua 39 | local Red = require(Path.To.Red) 40 | local Guard = require(Path.To.Guard) 41 | 42 | local CheckArg1 = Guard.Map(Guard.String, Guard.Number) 43 | local CheckArg2 = Guard.List(Guard.Vector3) 44 | local CheckArg3 = Guard.Boolean 45 | 46 | local CheckRet1 = Guard.String 47 | local CheckRet2 = Guard.Number 48 | local CheckRet3 = Guard.Set(Guard.String) 49 | 50 | return Red.Function("FunctionName", function(Arg1, Arg2, Arg3) 51 | return CheckArg1(Arg1), CheckArg2(Arg2), CheckArg3(Arg3) 52 | end, function(Ret1, Ret2, Ret3) 53 | return CheckRet1(Ret1), CheckRet2(Ret2), CheckRet3(Ret3) 54 | end) 55 | ``` 56 | 57 | ::: 58 | 59 | ## Setting the Callback 60 | 61 | A singular callback must be set on the server to allow clients to call the function. This callback is given the arguments and must return the expected return values. This callback may only be set on the server, attempting to set it on the client will result in an error. 62 | 63 | ```lua 64 | local Function = require(Path.To.Function) 65 | 66 | Function:SetCallback(function(Player, Arg1, Arg2, Arg3) 67 | return Arg1, Arg2, Arg3 68 | end) 69 | ``` 70 | 71 | ## Calling 72 | 73 | Functions can only be called from the client. The client must pass valid arguments to the function, and will be given back a [Future](https://util.redblox.dev/future) that completes with the returned values. 74 | 75 | It also returns a success boolean, similar to pcall, which is false if the server's callback function errored, and true if it was successful. 76 | 77 | ```lua 78 | local Function = require(Path.To.Function) 79 | 80 | local Success, Ret1, Ret2, Ret3 = Function:Call("Hello", 1, true):Await() 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/guide/introduction/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Red is available for Rojo-based workflows and Studio workflows through wally. 4 | 5 | ## Rojo 6 | 7 | 1. Make sure you have [Rojo](https://rojo.space/) and [Wally](https://wally.run/) installed. 8 | 2. Add Red to your `wally.toml` file: 9 | 10 | ```toml 11 | [dependencies] 12 | Red = "red-blox/red@2.3.0" 13 | ``` 14 | 15 | 3. Run `wally install` to install packages. 16 | 17 | ## Studio 18 | 19 | 1. Make sure you have the [Studio Wally](https://github.com/fewkz/studio-wally) plugin installed. 20 | 2. Add Red to your packages table: 21 | 22 | ```lua 23 | return { 24 | studioWallyServer = "https://studio-wally.fewkz.com", 25 | packages = { 26 | Red = "red-blox/red@2.3.0" 27 | } 28 | } 29 | ``` 30 | 31 | 3. Install packages. 32 | -------------------------------------------------------------------------------- /docs/guide/introduction/redblox.md: -------------------------------------------------------------------------------- 1 | # Redblox 2 | 3 | The Redblox project contains the library Red, but also other useful libraries. 4 | 5 | - [Red](https://red.redblox.dev/) - A simple, fast, and powerful networking library. 6 | - [Util](https://util.redblox.dev/) - A collection of small utilities for roblox. 7 | - [Bin](https://util.redblox.dev/bin) - Manages cleanup for objects that cannot be garbage collected. 8 | - [Clock](https://util.redblox.dev/clock) - Calls a function at consistent intervals. 9 | - [Collection](https://util.redblox.dev/collection) - Handles instance addition and removal from collections. 10 | - [Fetch](https://util.redblox.dev/fetch) - A Future based HTTP request utility similar to Javascript's fetch. 11 | - [Future](https://util.redblox.dev/future) - A lightweight class to represent asynchronous functions. 12 | - [Guard](https://util.redblox.dev/guard) - A runtime type checker with support for luau types. 13 | - [Promise](https://util.redblox.dev/promise) - A Promise implementation that prioritizes speed and ease of use. 14 | - [Ratelimit](https://util.redblox.dev/ratelimit) - Ratelimits many keys in a very intuitive interface. 15 | - [Signal](https://util.redblox.dev/signal) - A signal implementation without connection objects. 16 | - [Spawn](https://util.redblox.dev/spawn) - A shared "fast spawn" function that reuses threads. 17 | -------------------------------------------------------------------------------- /docs/guide/introduction/what-is-red.md: -------------------------------------------------------------------------------- 1 | # What is Red? 2 | 3 | Red is a networking library for Roblox that combines blazingly fast performance with a simple API. With Red's tiny size, it is suitable for any project, from tiny experiments to games that serve millions of players. 4 | 5 | ## Features 6 | 7 | ### ⚡Blazingly Fast 8 | 9 | Red achieves high performance by packing events in a compressed format, using up to 75% less bandwidth. Red uses a single remote event with identifiers to reduce bandwidth, and increase performance. 10 | 11 | ### ⚙️No Config 12 | 13 | Red is designed to work out of the box with no configuration required. Just require the library and begin developing. No config means less boilerplate and more time to focus on what matters: your project. 14 | 15 | ### 🔐Type Safety 16 | 17 | Red is written with type safety in mind, so you can use Luau and get full intellisense and linting right in your editor. Full intellisense without any extra work means faster development with autocomplete and catching bugs before they happen. 18 | 19 | ### 🔎Obsfucation 20 | 21 | As part of it's optimization features, Red obsfucates network data to make it harder for cheaters to read networking logs. This makes it significantly harder for cheaters to reverse engineer and cheat in your game. 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | text: A simple, fast, and powerful networking library. 6 | image: 7 | src: /logo.png 8 | alt: Red Logo 9 | actions: 10 | - theme: brand 11 | text: Get Started 12 | link: /guide/introduction/what-is-red 13 | - theme: alt 14 | text: API Reference 15 | link: /2.0/Red 16 | - theme: alt 17 | text: View on GitHub 18 | link: https://github.com/red-blox/Red 19 | 20 | features: 21 | - icon: ⚡ 22 | title: Blazingly Fast 23 | details: Red achieves high performance by packing events in a compressed format, using up to 75% less bandwidth. 24 | - icon: ⚙️ 25 | title: No Config 26 | details: Red is designed to work out of the box with no configuration required. Just require the library and begin developing. 27 | - icon: 🔐 28 | title: Type Safety 29 | details: Red is written with type safety in mind, so you can use Luau and get full intellisense and linting right in your editor. 30 | - icon: 🔎 31 | title: Obsfucation 32 | details: As part of it's optimization features, Red obsfucates network data to make it harder for cheaters to read networking logs. 33 | --- -------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-blox/Red/9ceac4c3f08cf3efba74ddd4f2c527a71f3003a0/docs/public/favicon.png -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-blox/Red/9ceac4c3f08cf3efba74ddd4f2c527a71f3003a0/docs/public/logo.png -------------------------------------------------------------------------------- /lib/Event/Client.luau: -------------------------------------------------------------------------------- 1 | local Spawn = require(script.Parent.Parent.Parent.Spawn) 2 | 3 | local Net = require(script.Parent.Parent.Net) 4 | 5 | export type Client = { 6 | Id: string, 7 | 8 | Unreliable: boolean, 9 | 10 | Fire: (self: Client, T...) -> (), 11 | On: (self: Client, Callback: (T...) -> ()) -> (), 12 | } 13 | 14 | local function Fire(self: Client, ...: T...) 15 | if self.Unreliable then 16 | Net.Client.SendUnreliableEvent(self.Id, table.pack(...)) 17 | else 18 | Net.Client.SendReliableEvent(self.Id, table.pack(...)) 19 | end 20 | end 21 | 22 | local function On(self: Client, Callback: (T...) -> ()) 23 | Net.Client.SetListener(self.Id, function(Args) 24 | Spawn(Callback, table.unpack(Args)) 25 | end) 26 | end 27 | 28 | local function Client(Id: string, Unreliable: boolean): Client 29 | return { 30 | Id = Id, 31 | 32 | Unreliable = Unreliable, 33 | 34 | Fire = Fire, 35 | On = On, 36 | } :: any 37 | end 38 | 39 | return Client 40 | -------------------------------------------------------------------------------- /lib/Event/Server.luau: -------------------------------------------------------------------------------- 1 | local Players = game:GetService("Players") 2 | 3 | local Spawn = require(script.Parent.Parent.Parent.Spawn) 4 | 5 | local Net = require(script.Parent.Parent.Net) 6 | 7 | export type Server = { 8 | Id: string, 9 | Validate: (...unknown) -> T..., 10 | 11 | Unreliable: boolean, 12 | 13 | Fire: (self: Server, Player: Player, T...) -> (), 14 | FireAll: (self: Server, T...) -> (), 15 | FireAllExcept: (self: Server, Except: Player, T...) -> (), 16 | FireList: (self: Server, List: { Player }, T...) -> (), 17 | FireWithFilter: (self: Server, Filter: (Player) -> boolean, T...) -> (), 18 | 19 | On: (self: Server, Callback: (Player, T...) -> ()) -> (), 20 | } 21 | 22 | local function Fire(self: Server, Player: Player, ...: T...) 23 | if self.Unreliable then 24 | Net.Server.SendUnreliableEvent(Player, self.Id, table.pack(...)) 25 | else 26 | Net.Server.SendReliableEvent(Player, self.Id, table.pack(...)) 27 | end 28 | end 29 | 30 | local function FireAll(self: Server, ...: T...) 31 | local Args = table.pack(...) 32 | 33 | for _, Player in Players:GetPlayers() do 34 | if self.Unreliable then 35 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 36 | else 37 | Net.Server.SendReliableEvent(Player, self.Id, Args) 38 | end 39 | end 40 | end 41 | 42 | local function FireAllExcept(self: Server, Except: Player, ...: T...) 43 | local Args = table.pack(...) 44 | 45 | for _, Player in Players:GetPlayers() do 46 | if Player ~= Except then 47 | if self.Unreliable then 48 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 49 | else 50 | Net.Server.SendReliableEvent(Player, self.Id, Args) 51 | end 52 | end 53 | end 54 | end 55 | 56 | local function FireList(self: Server, List: { Player }, ...: T...) 57 | local Args = table.pack(...) 58 | 59 | for _, Player in List do 60 | if self.Unreliable then 61 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 62 | else 63 | Net.Server.SendReliableEvent(Player, self.Id, Args) 64 | end 65 | end 66 | end 67 | 68 | local function FireWithFilter(self: Server, Filter: (Player) -> boolean, ...: T...) 69 | local Args = table.pack(...) 70 | 71 | for _, Player in Players:GetPlayers() do 72 | if Filter(Player) then 73 | if self.Unreliable then 74 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 75 | else 76 | Net.Server.SendReliableEvent(Player, self.Id, Args) 77 | end 78 | end 79 | end 80 | end 81 | 82 | local function On(self: Server, Callback: (Player, T...) -> ()) 83 | Net.Server.SetListener(self.Id, function(Player, Args) 84 | Spawn(function(self: typeof(self), Player: Player, ...: any) 85 | if pcall(self.Validate, ...) then 86 | Callback(Player, ...) 87 | end 88 | end, self, Player, table.unpack(Args)) 89 | end) 90 | end 91 | 92 | local function Server(Id: string, Validate: (...unknown) -> T..., Unreliable: boolean): Server 93 | return { 94 | Id = Id, 95 | Validate = Validate, 96 | 97 | Unreliable = Unreliable, 98 | 99 | Fire = Fire, 100 | FireAll = FireAll, 101 | FireAllExcept = FireAllExcept, 102 | FireList = FireList, 103 | FireWithFilter = FireWithFilter, 104 | 105 | On = On, 106 | } :: any 107 | end 108 | 109 | return Server 110 | -------------------------------------------------------------------------------- /lib/Event/init.luau: -------------------------------------------------------------------------------- 1 | local RunService = game:GetService("RunService") 2 | 3 | local Identifier = require(script.Parent.Identifier) 4 | 5 | local ServerEvent = require(script.Server) 6 | local ClientEvent = require(script.Client) 7 | 8 | export type Event = { 9 | Id: string, 10 | Validate: (...unknown) -> T..., 11 | 12 | Unreliable: boolean, 13 | 14 | ServerEvent: ServerEvent.Server?, 15 | ClientEvent: ClientEvent.Client?, 16 | 17 | Server: (self: Event) -> ServerEvent.Server, 18 | Client: (self: Event) -> ClientEvent.Client, 19 | } 20 | 21 | local function Server(self: Event): ServerEvent.Server 22 | assert(RunService:IsServer(), "Server events can only be accessed from the server") 23 | 24 | if not self.ServerEvent then 25 | self.ServerEvent = ServerEvent(self.Id, self.Validate, self.Unreliable) 26 | end 27 | 28 | return self.ServerEvent :: any 29 | end 30 | 31 | local function Client(self: Event): ClientEvent.Client 32 | assert(RunService:IsClient(), "Client events can only be accessed from the client") 33 | 34 | if not self.ClientEvent then 35 | self.ClientEvent = ClientEvent(self.Id, self.Unreliable) :: any 36 | end 37 | 38 | return self.ClientEvent :: any 39 | end 40 | 41 | type EventOptions = { 42 | Name: string, 43 | Unreliable: boolean?, 44 | } 45 | 46 | local function Event(Config: string | EventOptions, Validate: (...unknown) -> T...): Event 47 | local Name, Unreliable 48 | 49 | if type(Config) == "string" then 50 | Name = Config 51 | Unreliable = false 52 | else 53 | Name = Config.Name 54 | Unreliable = Config.Unreliable or false 55 | end 56 | 57 | assert(not Identifier.Exists(Name), "Cannot use same name twice") 58 | 59 | return { 60 | Id = Identifier.Shared(Name):Await(), 61 | Validate = Validate, 62 | 63 | Unreliable = Unreliable, 64 | 65 | ServerEvent = nil, 66 | ClientEvent = nil, 67 | 68 | Server = Server, 69 | Client = Client, 70 | } :: any 71 | end 72 | 73 | return Event 74 | -------------------------------------------------------------------------------- /lib/Function.luau: -------------------------------------------------------------------------------- 1 | local RunService = game:GetService("RunService") 2 | 3 | local Future = require(script.Parent.Parent.Future) 4 | local Spawn = require(script.Parent.Parent.Spawn) 5 | 6 | local Net = require(script.Parent.Net) 7 | local Identifier = require(script.Parent.Identifier) 8 | 9 | export type Function = { 10 | Id: string, 11 | Validate: (...unknown) -> A..., 12 | 13 | SetCallback: (self: Function, Callback: (Player, A...) -> R...) -> (), 14 | Call: ( 15 | self: Function, 16 | A... 17 | ) -> typeof(Future.new(function(): (boolean, R...) 18 | return {} :: any 19 | end)), 20 | } 21 | 22 | local function SetCallback(self: Function, Callback: (Player, A...) -> R...) 23 | assert(RunService:IsServer(), "Cannot set callback to function on client") 24 | 25 | Net.Server.SetListener(self.Id, function(Player, Args) 26 | local CallId = table.remove(Args, 1) 27 | 28 | if type(CallId) ~= "string" then 29 | return 30 | end 31 | 32 | Spawn(function(Player: Player, CallId: string, ...: any) 33 | if pcall(self.Validate, ...) then 34 | Net.Server.SendCallReturn(Player, CallId, table.pack(pcall(Callback, Player, ...))) 35 | end 36 | end, Player, CallId, unpack(Args)) 37 | end) 38 | end 39 | 40 | local function Call(self: Function, ...: A...) 41 | return Future.new(function(...: any) 42 | local CallId = Identifier.Unique() 43 | 44 | return unpack(Net.Client.CallAsync(self.Id, table.pack(CallId, ...))) 45 | end, ...) 46 | end 47 | 48 | local function Function( 49 | Name: string, 50 | ValidateArg: (...unknown) -> A..., 51 | ValidateRet: (...unknown) -> R... 52 | ): Function 53 | assert(not Identifier.Exists(Name), "Cannot use same name twice") 54 | 55 | return { 56 | Id = Identifier.Shared(Name):Await(), 57 | Validate = ValidateArg, 58 | 59 | SetCallback = SetCallback, 60 | Call = Call, 61 | } :: any 62 | end 63 | 64 | return Function 65 | -------------------------------------------------------------------------------- /lib/Identifier.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local RunService = game:GetService("RunService") 3 | 4 | local Future = require(script.Parent.Parent.Future) 5 | 6 | local Remote = ReplicatedStorage:WaitForChild("ReliableRedEvent") 7 | local Identifier = {} 8 | 9 | local NextShared = 0 10 | local NextUnique = 0 11 | 12 | local function UInt(Integer: number) 13 | return string.pack(`I{math.ceil(math.log(Integer + 1, 2) / 8)}`, Integer) 14 | end 15 | 16 | function Identifier.Shared(Name: string) 17 | return Future.new(function(Name: string) 18 | if RunService:IsServer() then 19 | if Remote:GetAttribute(Name) then 20 | return Remote:GetAttribute(Name) 21 | else 22 | NextShared += 1 23 | local Id = UInt(NextShared) 24 | 25 | Remote:SetAttribute(Name, Id) 26 | 27 | return Id 28 | end 29 | else 30 | local delayWarn = task.delay(5, function() 31 | warn(`Yielded while initializing identifier: {Name}. It may not exist on the server!`) 32 | end) 33 | 34 | while not Remote:GetAttribute(Name) do 35 | Remote.AttributeChanged:Wait() 36 | end 37 | 38 | task.cancel(delayWarn) 39 | 40 | return Remote:GetAttribute(Name) 41 | end 42 | end, Name) 43 | end 44 | 45 | function Identifier.Exists(Name: string) 46 | return RunService:IsServer() and Remote:GetAttribute(Name) ~= nil 47 | end 48 | 49 | function Identifier.Unique() 50 | NextUnique += 1 51 | 52 | if NextUnique == 0xFFFF then 53 | NextUnique = 0 54 | end 55 | 56 | return UInt(NextUnique) 57 | end 58 | 59 | return Identifier 60 | -------------------------------------------------------------------------------- /lib/Net/Client.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local RunService = game:GetService("RunService") 3 | 4 | local ReliableRemote = ReplicatedStorage:WaitForChild("ReliableRedEvent") :: RemoteEvent 5 | local UnreliableRemote = ReplicatedStorage:WaitForChild("UnreliableRedEvent") :: RemoteEvent 6 | 7 | local ListenerMap: { [string]: ({ any }) -> () } = {} 8 | local CallMap: { [string]: thread } = {} 9 | local Outgoing: { 10 | Reliable: { [string]: { { any } } }, 11 | Call: { [string]: { { any } } }, 12 | } = { 13 | Reliable = {}, 14 | Call = {}, 15 | } 16 | 17 | local function SendReliableEvent(EventId: string, CallList: { any }) 18 | if not Outgoing.Reliable[EventId] then 19 | Outgoing.Reliable[EventId] = {} 20 | end 21 | 22 | table.insert(Outgoing.Reliable[EventId], CallList) 23 | end 24 | 25 | local function SendUnreliableEvent(EventId: string, CallList: { any }) 26 | UnreliableRemote:FireServer(EventId, CallList) 27 | end 28 | 29 | local function SetListener(EventId: string, Callback: ({ any }) -> ()) 30 | ListenerMap[EventId] = Callback 31 | end 32 | 33 | local function CallAsync(EventId: string, CallList: { any }): { any } 34 | if not Outgoing.Call[EventId] then 35 | Outgoing.Call[EventId] = {} 36 | end 37 | 38 | table.insert(Outgoing.Call[EventId], CallList) 39 | CallMap[CallList[1]] = coroutine.running() 40 | 41 | return coroutine.yield() 42 | end 43 | 44 | local function Start() 45 | ReliableRemote.OnClientEvent:Connect(function(IncomingFireSection, IncomingCallSection) 46 | if IncomingFireSection then 47 | for EventId, CallList in IncomingFireSection do 48 | local Listener = ListenerMap[EventId] 49 | 50 | if Listener then 51 | for _, Call in CallList do 52 | Listener(Call) 53 | end 54 | end 55 | end 56 | end 57 | 58 | if IncomingCallSection then 59 | for CallId, CallList in IncomingCallSection do 60 | local CallThread = CallMap[CallId] 61 | 62 | if CallThread then 63 | CallMap[CallId] = nil 64 | coroutine.resume(CallThread, CallList) 65 | end 66 | end 67 | end 68 | end) 69 | 70 | UnreliableRemote.OnClientEvent:Connect(function(IncomingEventId, IncomingArgs) 71 | local Listener = ListenerMap[IncomingEventId] 72 | 73 | if Listener then 74 | Listener(IncomingArgs) 75 | end 76 | end) 77 | 78 | RunService.Heartbeat:Connect(function() 79 | if next(Outgoing.Reliable) or next(Outgoing.Call) then 80 | ReliableRemote:FireServer(Outgoing.Reliable, Outgoing.Call) 81 | 82 | Outgoing.Reliable = {} 83 | Outgoing.Call = {} 84 | end 85 | end) 86 | end 87 | 88 | return { 89 | SendReliableEvent = SendReliableEvent, 90 | SendUnreliableEvent = SendUnreliableEvent, 91 | SetListener = SetListener, 92 | CallAsync = CallAsync, 93 | Start = Start, 94 | } 95 | -------------------------------------------------------------------------------- /lib/Net/Server.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local RunService = game:GetService("RunService") 3 | 4 | local Guard = require(script.Parent.Parent.Parent.Guard) 5 | 6 | local ReliableRemote = ReplicatedStorage:WaitForChild("ReliableRedEvent") :: RemoteEvent 7 | local UnreliableRemote = ReplicatedStorage:WaitForChild("UnreliableRedEvent") :: RemoteEvent 8 | 9 | local FireSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) 10 | local CallSectionCheck = Guard.Check(Guard.Map(Guard.String, Guard.List(Guard.Any))) 11 | 12 | local ListenerMap: { [string]: (Player, { any }) -> () } = {} 13 | local OutgoingMap: { 14 | [Player]: { 15 | Reliable: { [string]: { { any } } }, 16 | CallReturn: { [string]: { any } }, 17 | }, 18 | } = {} 19 | 20 | local function SendReliableEvent(Player: Player, EventId: string, Args: { any }) 21 | if not OutgoingMap[Player] then 22 | OutgoingMap[Player] = { 23 | Reliable = {}, 24 | CallReturn = {}, 25 | } 26 | end 27 | 28 | if not OutgoingMap[Player].Reliable[EventId] then 29 | OutgoingMap[Player].Reliable[EventId] = {} 30 | end 31 | 32 | table.insert(OutgoingMap[Player].Reliable[EventId], Args) 33 | end 34 | 35 | local function SendUnreliableEvent(Player: Player, EventId: string, Args: { any }) 36 | UnreliableRemote:FireClient(Player, EventId, Args) 37 | end 38 | 39 | local function SetListener(EventId: string, Callback: (Player, { any }) -> ()) 40 | ListenerMap[EventId] = Callback 41 | end 42 | 43 | local function SendCallReturn(Player: Player, CallId: string, Args: { any }) 44 | if not OutgoingMap[Player] then 45 | OutgoingMap[Player] = { 46 | Reliable = {}, 47 | CallReturn = {}, 48 | } 49 | end 50 | 51 | OutgoingMap[Player].CallReturn[CallId] = Args 52 | end 53 | 54 | local function Start() 55 | ReliableRemote.OnServerEvent:Connect(function(Player, IncomingFireSection, IncomingCallSection) 56 | local FireSectionValid, FireSection = FireSectionCheck(IncomingFireSection) 57 | 58 | if FireSectionValid then 59 | for EventId, CallList in FireSection do 60 | local Callback = ListenerMap[EventId] 61 | 62 | if Callback then 63 | for _, Call in CallList do 64 | Callback(Player, Call) 65 | end 66 | end 67 | end 68 | end 69 | 70 | local CallSectionValid, CallSection = CallSectionCheck(IncomingCallSection) 71 | 72 | if CallSectionValid then 73 | for EventId, CallList in CallSection do 74 | local Callback = ListenerMap[EventId] 75 | 76 | if Callback then 77 | for _, Call in CallList do 78 | Callback(Player, Call) 79 | end 80 | else 81 | for _, Call in CallList do 82 | local CallId = Call[1] 83 | 84 | SendCallReturn(Player, CallId, { false, "Event has no listener." }) 85 | end 86 | end 87 | end 88 | end 89 | end) 90 | 91 | UnreliableRemote.OnServerEvent:Connect(function(Player, IncomingEventId, IncomingArgs) 92 | if type(IncomingEventId) == "string" and type(IncomingArgs) == "table" then 93 | local Callback = ListenerMap[IncomingEventId] 94 | 95 | if Callback then 96 | Callback(Player, IncomingArgs) 97 | end 98 | end 99 | end) 100 | 101 | RunService.Heartbeat:Connect(function() 102 | for Player, Outgoing in OutgoingMap do 103 | if next(Outgoing.Reliable) or next(Outgoing.CallReturn) then 104 | ReliableRemote:FireClient(Player, Outgoing.Reliable, Outgoing.CallReturn) 105 | end 106 | 107 | OutgoingMap[Player] = nil 108 | end 109 | end) 110 | end 111 | 112 | return { 113 | SendReliableEvent = SendReliableEvent, 114 | SendUnreliableEvent = SendUnreliableEvent, 115 | SetListener = SetListener, 116 | SendCallReturn = SendCallReturn, 117 | Start = Start, 118 | } 119 | -------------------------------------------------------------------------------- /lib/Net/init.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Server = require(script.Server), 3 | Client = require(script.Client), 4 | } 5 | -------------------------------------------------------------------------------- /lib/SharedEvent.luau: -------------------------------------------------------------------------------- 1 | -- For implementation discussion, see: https://github.com/red-blox/Red/issues/8 2 | 3 | local Players = game:GetService("Players") 4 | local RunService = game:GetService("RunService") 5 | 6 | local Identifier = require(script.Parent.Identifier) 7 | local Spawn = require(script.Parent.Parent.Spawn) 8 | local Signal = require(script.Parent.Parent.Signal) 9 | 10 | local Net = require(script.Parent.Net) 11 | 12 | type SharedBaseEvent = { 13 | Id: string, 14 | Unreliable: boolean, 15 | 16 | FireClient: (self: SharedBaseEvent, Player: Player, T...) -> (), 17 | FireAllClients: (self: SharedBaseEvent, T...) -> (), 18 | FireAllClientsExcept: (self: SharedBaseEvent, Player: Player, T...) -> (), 19 | FireClients: (self: SharedBaseEvent, Players: { Player }, T...) -> (), 20 | FireFilteredClients: (self: SharedBaseEvent, Filter: (Player) -> boolean, T...) -> (), 21 | 22 | FireServer: (self: SharedBaseEvent, T...) -> (), 23 | } 24 | 25 | export type SharedCallEvent = SharedBaseEvent & { 26 | CallMode: "Call", 27 | 28 | Listener: ((...any) -> ())?, 29 | 30 | SetServerListener: (self: SharedCallEvent, Listener: (Player: Player, T...) -> ()) -> (), 31 | SetClientListener: (self: SharedCallEvent, Listener: (T...) -> ()) -> (), 32 | } 33 | 34 | export type SharedSignalEvent = SharedBaseEvent & { 35 | CallMode: "Signal", 36 | 37 | Signal: Signal.Signal<...any>, 38 | 39 | OnServer: (self: SharedSignalEvent, Listener: (Player: Player, T...) -> ()) -> () -> (), 40 | OnClient: (self: SharedSignalEvent, Listener: (T...) -> ()) -> () -> (), 41 | } 42 | 43 | local function FireClient(self: SharedBaseEvent, Player: Player, ...) 44 | assert(RunService:IsServer(), "FireClient can only be called from the server") 45 | 46 | if self.Unreliable then 47 | Net.Server.SendUnreliableEvent(Player, self.Id, table.pack(...)) 48 | else 49 | Net.Server.SendReliableEvent(Player, self.Id, table.pack(...)) 50 | end 51 | end 52 | 53 | local function FireAllClients(self: SharedBaseEvent, ...) 54 | assert(RunService:IsServer(), "FireAllClients can only be called from the server") 55 | 56 | local Args = table.pack(...) 57 | 58 | if self.Unreliable then 59 | for _, Player in Players:GetPlayers() do 60 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 61 | end 62 | else 63 | for _, Player in Players:GetPlayers() do 64 | Net.Server.SendReliableEvent(Player, self.Id, Args) 65 | end 66 | end 67 | end 68 | 69 | local function FireAllClientsExcept(self: SharedBaseEvent, Player: Player, ...) 70 | assert(RunService:IsServer(), "FireAllClientsExcept can only be called from the server") 71 | 72 | local Args = table.pack(...) 73 | 74 | if self.Unreliable then 75 | for _, OtherPlayer in Players:GetPlayers() do 76 | if OtherPlayer ~= Player then 77 | Net.Server.SendUnreliableEvent(OtherPlayer, self.Id, Args) 78 | end 79 | end 80 | else 81 | for _, OtherPlayer in Players:GetPlayers() do 82 | if OtherPlayer ~= Player then 83 | Net.Server.SendReliableEvent(OtherPlayer, self.Id, Args) 84 | end 85 | end 86 | end 87 | end 88 | 89 | local function FireClients(self: SharedBaseEvent, Players: { Player }, ...) 90 | assert(RunService:IsServer(), "FireClients can only be called from the server") 91 | 92 | local Args = table.pack(...) 93 | 94 | if self.Unreliable then 95 | for _, Player in Players do 96 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 97 | end 98 | else 99 | for _, Player in Players do 100 | Net.Server.SendReliableEvent(Player, self.Id, Args) 101 | end 102 | end 103 | end 104 | 105 | local function FireFilteredClients(self: SharedBaseEvent, Filter: (Player) -> boolean, ...) 106 | assert(RunService:IsServer(), "FireFilteredClients can only be called from the server") 107 | 108 | local Args = table.pack(...) 109 | 110 | for _, Player in Players:GetPlayers() do 111 | if Filter(Player) then 112 | if self.Unreliable then 113 | Net.Server.SendUnreliableEvent(Player, self.Id, Args) 114 | else 115 | Net.Server.SendReliableEvent(Player, self.Id, Args) 116 | end 117 | end 118 | end 119 | end 120 | 121 | local function FireServer(self: SharedBaseEvent, ...) 122 | assert(RunService:IsClient(), "FireServer can only be called from the client") 123 | 124 | local Args = table.pack(...) 125 | 126 | if self.Unreliable then 127 | Net.Client.SendUnreliableEvent(self.Id, Args) 128 | else 129 | Net.Client.SendReliableEvent(self.Id, Args) 130 | end 131 | end 132 | 133 | local function SetServerListener(self: SharedCallEvent, Listener: (Player: Player, T...) -> ()) 134 | assert(RunService:IsServer(), "SetServerListener can only be called from the server") 135 | 136 | self.Listener = Listener :: any 137 | end 138 | 139 | local function SetClientListener(self: SharedCallEvent, Listener: (T...) -> ()) 140 | assert(RunService:IsClient(), "SetClientListener can only be called from the client") 141 | 142 | self.Listener = Listener :: any 143 | end 144 | 145 | local function OnServer(self: SharedSignalEvent, Listener: (Player: Player, T...) -> ()): () -> () 146 | assert(RunService:IsServer(), "OnServer can only be called from the server") 147 | 148 | return self.Signal:Connect(Listener :: any) 149 | end 150 | 151 | local function OnClient(self: SharedSignalEvent, Listener: (T...) -> ()): () -> () 152 | assert(RunService:IsClient(), "OnClient can only be called from the client") 153 | 154 | return self.Signal:Connect(Listener :: any) 155 | end 156 | 157 | type SharedEventOptions = { 158 | Name: string, 159 | Unreliable: boolean?, 160 | } 161 | 162 | type ValidateFunction = (...unknown) -> T... 163 | 164 | local function SharedBaseEvent(Name: string, Unreliable: boolean): SharedBaseEvent 165 | local self = { 166 | Id = Identifier.Shared(Name):Await(), 167 | Unreliable = Unreliable, 168 | 169 | FireClient = FireClient, 170 | FireAllClients = FireAllClients, 171 | FireAllClientsExcept = FireAllClientsExcept, 172 | FireClients = FireClients, 173 | FireFilteredClients = FireFilteredClients, 174 | 175 | FireServer = FireServer, 176 | } 177 | 178 | return self :: any 179 | end 180 | 181 | local function SharedCallEvent( 182 | Options: string | SharedEventOptions, 183 | Validate: ValidateFunction 184 | ): SharedCallEvent 185 | local ParsedOptions = if typeof(Options) ~= "string" 186 | then Options 187 | else { 188 | Name = Options, 189 | Unreliable = false, 190 | } 191 | 192 | local self: any = SharedBaseEvent(ParsedOptions.Name, ParsedOptions.Unreliable or false) 193 | 194 | self.CallMode = "Call" 195 | 196 | self.SetServerListener = SetServerListener 197 | self.SetClientListener = SetClientListener 198 | 199 | self.Listener = nil :: (...any) -> ()? 200 | 201 | if RunService:IsServer() then 202 | Net.Server.SetListener(self.Id, function(Player, Args) 203 | Spawn(function(Player: Player, ...: any) 204 | if self.Listener and pcall(Validate, ...) then 205 | self.Listener(Player, ...) 206 | end 207 | end, Player, table.unpack(Args)) 208 | end) 209 | else 210 | Net.Client.SetListener(self.Id, function(Args) 211 | Spawn(function(...: any) 212 | if self.Listener and pcall(Validate, ...) then 213 | self.Listener(...) 214 | end 215 | end, table.unpack(Args)) 216 | end) 217 | end 218 | 219 | return self 220 | end 221 | 222 | local function SharedSignalEvent( 223 | Options: string | SharedEventOptions, 224 | Validate: ValidateFunction 225 | ): SharedSignalEvent 226 | local ParsedOptions = if typeof(Options) ~= "string" 227 | then Options 228 | else { 229 | Name = Options, 230 | Unreliable = false, 231 | } 232 | 233 | local self: any = SharedBaseEvent(ParsedOptions.Name, ParsedOptions.Unreliable or false) 234 | 235 | self.CallMode = "Signal" 236 | 237 | self.Signal = Signal() 238 | 239 | self.OnServer = OnServer 240 | self.OnClient = OnClient 241 | 242 | if RunService:IsServer() then 243 | Net.Server.SetListener(self.Id, function(Player, Args) 244 | Spawn(function(Player: Player, ...: any) 245 | if pcall(Validate, ...) then 246 | self.Signal:Fire(Player, ...) 247 | end 248 | end, Player, table.unpack(Args)) 249 | end) 250 | else 251 | Net.Client.SetListener(self.Id, function(Args) 252 | Spawn(function(...: any) 253 | if pcall(Validate, ...) then 254 | self.Signal:Fire(...) 255 | end 256 | end, table.unpack(Args)) 257 | end) 258 | end 259 | 260 | return self :: SharedSignalEvent 261 | end 262 | 263 | return { 264 | SharedCallEvent = SharedCallEvent, 265 | SharedSignalEvent = SharedSignalEvent, 266 | } 267 | -------------------------------------------------------------------------------- /lib/init.luau: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local RunService = game:GetService("RunService") 3 | 4 | if RunService:IsServer() then 5 | if not ReplicatedStorage:FindFirstChild("ReliableRedEvent") then 6 | local ReliableRemote = Instance.new("RemoteEvent") 7 | ReliableRemote.Name = "ReliableRedEvent" 8 | ReliableRemote.Parent = ReplicatedStorage 9 | end 10 | 11 | if not ReplicatedStorage:FindFirstChild("UnreliableRedEvent") then 12 | local UnreliableRemote = Instance.new("UnreliableRemoteEvent") 13 | UnreliableRemote.Name = "UnreliableRedEvent" 14 | UnreliableRemote.Parent = ReplicatedStorage 15 | end 16 | 17 | require(script.Net).Server.Start() 18 | else 19 | ReplicatedStorage:WaitForChild("ReliableRedEvent") 20 | ReplicatedStorage:WaitForChild("UnreliableRedEvent") 21 | 22 | require(script.Net).Client.Start() 23 | end 24 | 25 | local SharedEvents = require(script.SharedEvent) 26 | 27 | return { 28 | Event = require(script.Event), 29 | Function = require(script.Function), 30 | SharedEvent = SharedEvents.SharedCallEvent, 31 | SharedSignalEvent = SharedEvents.SharedSignalEvent, 32 | } 33 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Red", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "vitepress": "^1.0.0-rc.20", 9 | "vue": "^3.3.4" 10 | } 11 | }, 12 | "node_modules/@algolia/autocomplete-core": { 13 | "version": "1.9.3", 14 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", 15 | "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", 16 | "dev": true, 17 | "dependencies": { 18 | "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", 19 | "@algolia/autocomplete-shared": "1.9.3" 20 | } 21 | }, 22 | "node_modules/@algolia/autocomplete-plugin-algolia-insights": { 23 | "version": "1.9.3", 24 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", 25 | "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", 26 | "dev": true, 27 | "dependencies": { 28 | "@algolia/autocomplete-shared": "1.9.3" 29 | }, 30 | "peerDependencies": { 31 | "search-insights": ">= 1 < 3" 32 | } 33 | }, 34 | "node_modules/@algolia/autocomplete-preset-algolia": { 35 | "version": "1.9.3", 36 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", 37 | "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", 38 | "dev": true, 39 | "dependencies": { 40 | "@algolia/autocomplete-shared": "1.9.3" 41 | }, 42 | "peerDependencies": { 43 | "@algolia/client-search": ">= 4.9.1 < 6", 44 | "algoliasearch": ">= 4.9.1 < 6" 45 | } 46 | }, 47 | "node_modules/@algolia/autocomplete-shared": { 48 | "version": "1.9.3", 49 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", 50 | "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", 51 | "dev": true, 52 | "peerDependencies": { 53 | "@algolia/client-search": ">= 4.9.1 < 6", 54 | "algoliasearch": ">= 4.9.1 < 6" 55 | } 56 | }, 57 | "node_modules/@algolia/cache-browser-local-storage": { 58 | "version": "4.19.1", 59 | "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.19.1.tgz", 60 | "integrity": "sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==", 61 | "dev": true, 62 | "dependencies": { 63 | "@algolia/cache-common": "4.19.1" 64 | } 65 | }, 66 | "node_modules/@algolia/cache-common": { 67 | "version": "4.19.1", 68 | "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.19.1.tgz", 69 | "integrity": "sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==", 70 | "dev": true 71 | }, 72 | "node_modules/@algolia/cache-in-memory": { 73 | "version": "4.19.1", 74 | "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.19.1.tgz", 75 | "integrity": "sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==", 76 | "dev": true, 77 | "dependencies": { 78 | "@algolia/cache-common": "4.19.1" 79 | } 80 | }, 81 | "node_modules/@algolia/client-account": { 82 | "version": "4.19.1", 83 | "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.19.1.tgz", 84 | "integrity": "sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==", 85 | "dev": true, 86 | "dependencies": { 87 | "@algolia/client-common": "4.19.1", 88 | "@algolia/client-search": "4.19.1", 89 | "@algolia/transporter": "4.19.1" 90 | } 91 | }, 92 | "node_modules/@algolia/client-analytics": { 93 | "version": "4.19.1", 94 | "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.19.1.tgz", 95 | "integrity": "sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==", 96 | "dev": true, 97 | "dependencies": { 98 | "@algolia/client-common": "4.19.1", 99 | "@algolia/client-search": "4.19.1", 100 | "@algolia/requester-common": "4.19.1", 101 | "@algolia/transporter": "4.19.1" 102 | } 103 | }, 104 | "node_modules/@algolia/client-common": { 105 | "version": "4.19.1", 106 | "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.19.1.tgz", 107 | "integrity": "sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==", 108 | "dev": true, 109 | "dependencies": { 110 | "@algolia/requester-common": "4.19.1", 111 | "@algolia/transporter": "4.19.1" 112 | } 113 | }, 114 | "node_modules/@algolia/client-personalization": { 115 | "version": "4.19.1", 116 | "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.19.1.tgz", 117 | "integrity": "sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==", 118 | "dev": true, 119 | "dependencies": { 120 | "@algolia/client-common": "4.19.1", 121 | "@algolia/requester-common": "4.19.1", 122 | "@algolia/transporter": "4.19.1" 123 | } 124 | }, 125 | "node_modules/@algolia/client-search": { 126 | "version": "4.19.1", 127 | "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.19.1.tgz", 128 | "integrity": "sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==", 129 | "dev": true, 130 | "dependencies": { 131 | "@algolia/client-common": "4.19.1", 132 | "@algolia/requester-common": "4.19.1", 133 | "@algolia/transporter": "4.19.1" 134 | } 135 | }, 136 | "node_modules/@algolia/logger-common": { 137 | "version": "4.19.1", 138 | "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.19.1.tgz", 139 | "integrity": "sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==", 140 | "dev": true 141 | }, 142 | "node_modules/@algolia/logger-console": { 143 | "version": "4.19.1", 144 | "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.19.1.tgz", 145 | "integrity": "sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==", 146 | "dev": true, 147 | "dependencies": { 148 | "@algolia/logger-common": "4.19.1" 149 | } 150 | }, 151 | "node_modules/@algolia/requester-browser-xhr": { 152 | "version": "4.19.1", 153 | "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.19.1.tgz", 154 | "integrity": "sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==", 155 | "dev": true, 156 | "dependencies": { 157 | "@algolia/requester-common": "4.19.1" 158 | } 159 | }, 160 | "node_modules/@algolia/requester-common": { 161 | "version": "4.19.1", 162 | "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.19.1.tgz", 163 | "integrity": "sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==", 164 | "dev": true 165 | }, 166 | "node_modules/@algolia/requester-node-http": { 167 | "version": "4.19.1", 168 | "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.19.1.tgz", 169 | "integrity": "sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==", 170 | "dev": true, 171 | "dependencies": { 172 | "@algolia/requester-common": "4.19.1" 173 | } 174 | }, 175 | "node_modules/@algolia/transporter": { 176 | "version": "4.19.1", 177 | "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.19.1.tgz", 178 | "integrity": "sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==", 179 | "dev": true, 180 | "dependencies": { 181 | "@algolia/cache-common": "4.19.1", 182 | "@algolia/logger-common": "4.19.1", 183 | "@algolia/requester-common": "4.19.1" 184 | } 185 | }, 186 | "node_modules/@babel/parser": { 187 | "version": "7.22.4", 188 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", 189 | "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", 190 | "dev": true, 191 | "bin": { 192 | "parser": "bin/babel-parser.js" 193 | }, 194 | "engines": { 195 | "node": ">=6.0.0" 196 | } 197 | }, 198 | "node_modules/@docsearch/css": { 199 | "version": "3.5.2", 200 | "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", 201 | "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", 202 | "dev": true 203 | }, 204 | "node_modules/@docsearch/js": { 205 | "version": "3.5.2", 206 | "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", 207 | "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", 208 | "dev": true, 209 | "dependencies": { 210 | "@docsearch/react": "3.5.2", 211 | "preact": "^10.0.0" 212 | } 213 | }, 214 | "node_modules/@docsearch/react": { 215 | "version": "3.5.2", 216 | "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", 217 | "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", 218 | "dev": true, 219 | "dependencies": { 220 | "@algolia/autocomplete-core": "1.9.3", 221 | "@algolia/autocomplete-preset-algolia": "1.9.3", 222 | "@docsearch/css": "3.5.2", 223 | "algoliasearch": "^4.19.1" 224 | }, 225 | "peerDependencies": { 226 | "@types/react": ">= 16.8.0 < 19.0.0", 227 | "react": ">= 16.8.0 < 19.0.0", 228 | "react-dom": ">= 16.8.0 < 19.0.0", 229 | "search-insights": ">= 1 < 3" 230 | }, 231 | "peerDependenciesMeta": { 232 | "@types/react": { 233 | "optional": true 234 | }, 235 | "react": { 236 | "optional": true 237 | }, 238 | "react-dom": { 239 | "optional": true 240 | }, 241 | "search-insights": { 242 | "optional": true 243 | } 244 | } 245 | }, 246 | "node_modules/@esbuild/android-arm": { 247 | "version": "0.18.20", 248 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 249 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 250 | "cpu": [ 251 | "arm" 252 | ], 253 | "dev": true, 254 | "optional": true, 255 | "os": [ 256 | "android" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/@esbuild/android-arm64": { 263 | "version": "0.18.20", 264 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 265 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 266 | "cpu": [ 267 | "arm64" 268 | ], 269 | "dev": true, 270 | "optional": true, 271 | "os": [ 272 | "android" 273 | ], 274 | "engines": { 275 | "node": ">=12" 276 | } 277 | }, 278 | "node_modules/@esbuild/android-x64": { 279 | "version": "0.18.20", 280 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 281 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 282 | "cpu": [ 283 | "x64" 284 | ], 285 | "dev": true, 286 | "optional": true, 287 | "os": [ 288 | "android" 289 | ], 290 | "engines": { 291 | "node": ">=12" 292 | } 293 | }, 294 | "node_modules/@esbuild/darwin-arm64": { 295 | "version": "0.18.20", 296 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 297 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 298 | "cpu": [ 299 | "arm64" 300 | ], 301 | "dev": true, 302 | "optional": true, 303 | "os": [ 304 | "darwin" 305 | ], 306 | "engines": { 307 | "node": ">=12" 308 | } 309 | }, 310 | "node_modules/@esbuild/darwin-x64": { 311 | "version": "0.18.20", 312 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 313 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 314 | "cpu": [ 315 | "x64" 316 | ], 317 | "dev": true, 318 | "optional": true, 319 | "os": [ 320 | "darwin" 321 | ], 322 | "engines": { 323 | "node": ">=12" 324 | } 325 | }, 326 | "node_modules/@esbuild/freebsd-arm64": { 327 | "version": "0.18.20", 328 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 329 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 330 | "cpu": [ 331 | "arm64" 332 | ], 333 | "dev": true, 334 | "optional": true, 335 | "os": [ 336 | "freebsd" 337 | ], 338 | "engines": { 339 | "node": ">=12" 340 | } 341 | }, 342 | "node_modules/@esbuild/freebsd-x64": { 343 | "version": "0.18.20", 344 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 345 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 346 | "cpu": [ 347 | "x64" 348 | ], 349 | "dev": true, 350 | "optional": true, 351 | "os": [ 352 | "freebsd" 353 | ], 354 | "engines": { 355 | "node": ">=12" 356 | } 357 | }, 358 | "node_modules/@esbuild/linux-arm": { 359 | "version": "0.18.20", 360 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 361 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 362 | "cpu": [ 363 | "arm" 364 | ], 365 | "dev": true, 366 | "optional": true, 367 | "os": [ 368 | "linux" 369 | ], 370 | "engines": { 371 | "node": ">=12" 372 | } 373 | }, 374 | "node_modules/@esbuild/linux-arm64": { 375 | "version": "0.18.20", 376 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 377 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 378 | "cpu": [ 379 | "arm64" 380 | ], 381 | "dev": true, 382 | "optional": true, 383 | "os": [ 384 | "linux" 385 | ], 386 | "engines": { 387 | "node": ">=12" 388 | } 389 | }, 390 | "node_modules/@esbuild/linux-ia32": { 391 | "version": "0.18.20", 392 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 393 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 394 | "cpu": [ 395 | "ia32" 396 | ], 397 | "dev": true, 398 | "optional": true, 399 | "os": [ 400 | "linux" 401 | ], 402 | "engines": { 403 | "node": ">=12" 404 | } 405 | }, 406 | "node_modules/@esbuild/linux-loong64": { 407 | "version": "0.18.20", 408 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 409 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 410 | "cpu": [ 411 | "loong64" 412 | ], 413 | "dev": true, 414 | "optional": true, 415 | "os": [ 416 | "linux" 417 | ], 418 | "engines": { 419 | "node": ">=12" 420 | } 421 | }, 422 | "node_modules/@esbuild/linux-mips64el": { 423 | "version": "0.18.20", 424 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 425 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 426 | "cpu": [ 427 | "mips64el" 428 | ], 429 | "dev": true, 430 | "optional": true, 431 | "os": [ 432 | "linux" 433 | ], 434 | "engines": { 435 | "node": ">=12" 436 | } 437 | }, 438 | "node_modules/@esbuild/linux-ppc64": { 439 | "version": "0.18.20", 440 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 441 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 442 | "cpu": [ 443 | "ppc64" 444 | ], 445 | "dev": true, 446 | "optional": true, 447 | "os": [ 448 | "linux" 449 | ], 450 | "engines": { 451 | "node": ">=12" 452 | } 453 | }, 454 | "node_modules/@esbuild/linux-riscv64": { 455 | "version": "0.18.20", 456 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 457 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 458 | "cpu": [ 459 | "riscv64" 460 | ], 461 | "dev": true, 462 | "optional": true, 463 | "os": [ 464 | "linux" 465 | ], 466 | "engines": { 467 | "node": ">=12" 468 | } 469 | }, 470 | "node_modules/@esbuild/linux-s390x": { 471 | "version": "0.18.20", 472 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 473 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 474 | "cpu": [ 475 | "s390x" 476 | ], 477 | "dev": true, 478 | "optional": true, 479 | "os": [ 480 | "linux" 481 | ], 482 | "engines": { 483 | "node": ">=12" 484 | } 485 | }, 486 | "node_modules/@esbuild/linux-x64": { 487 | "version": "0.18.20", 488 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 489 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 490 | "cpu": [ 491 | "x64" 492 | ], 493 | "dev": true, 494 | "optional": true, 495 | "os": [ 496 | "linux" 497 | ], 498 | "engines": { 499 | "node": ">=12" 500 | } 501 | }, 502 | "node_modules/@esbuild/netbsd-x64": { 503 | "version": "0.18.20", 504 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 505 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 506 | "cpu": [ 507 | "x64" 508 | ], 509 | "dev": true, 510 | "optional": true, 511 | "os": [ 512 | "netbsd" 513 | ], 514 | "engines": { 515 | "node": ">=12" 516 | } 517 | }, 518 | "node_modules/@esbuild/openbsd-x64": { 519 | "version": "0.18.20", 520 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 521 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 522 | "cpu": [ 523 | "x64" 524 | ], 525 | "dev": true, 526 | "optional": true, 527 | "os": [ 528 | "openbsd" 529 | ], 530 | "engines": { 531 | "node": ">=12" 532 | } 533 | }, 534 | "node_modules/@esbuild/sunos-x64": { 535 | "version": "0.18.20", 536 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 537 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 538 | "cpu": [ 539 | "x64" 540 | ], 541 | "dev": true, 542 | "optional": true, 543 | "os": [ 544 | "sunos" 545 | ], 546 | "engines": { 547 | "node": ">=12" 548 | } 549 | }, 550 | "node_modules/@esbuild/win32-arm64": { 551 | "version": "0.18.20", 552 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 553 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 554 | "cpu": [ 555 | "arm64" 556 | ], 557 | "dev": true, 558 | "optional": true, 559 | "os": [ 560 | "win32" 561 | ], 562 | "engines": { 563 | "node": ">=12" 564 | } 565 | }, 566 | "node_modules/@esbuild/win32-ia32": { 567 | "version": "0.18.20", 568 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 569 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 570 | "cpu": [ 571 | "ia32" 572 | ], 573 | "dev": true, 574 | "optional": true, 575 | "os": [ 576 | "win32" 577 | ], 578 | "engines": { 579 | "node": ">=12" 580 | } 581 | }, 582 | "node_modules/@esbuild/win32-x64": { 583 | "version": "0.18.20", 584 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 585 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 586 | "cpu": [ 587 | "x64" 588 | ], 589 | "dev": true, 590 | "optional": true, 591 | "os": [ 592 | "win32" 593 | ], 594 | "engines": { 595 | "node": ">=12" 596 | } 597 | }, 598 | "node_modules/@jridgewell/sourcemap-codec": { 599 | "version": "1.4.15", 600 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 601 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 602 | "dev": true 603 | }, 604 | "node_modules/@types/linkify-it": { 605 | "version": "3.0.3", 606 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", 607 | "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", 608 | "dev": true 609 | }, 610 | "node_modules/@types/markdown-it": { 611 | "version": "13.0.2", 612 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.2.tgz", 613 | "integrity": "sha512-Tla7hH9oeXHOlJyBFdoqV61xWE9FZf/y2g+gFVwQ2vE1/eBzjUno5JCd3Hdb5oATve5OF6xNjZ/4VIZhVVx+hA==", 614 | "dev": true, 615 | "dependencies": { 616 | "@types/linkify-it": "*", 617 | "@types/mdurl": "*" 618 | } 619 | }, 620 | "node_modules/@types/mdurl": { 621 | "version": "1.0.3", 622 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.3.tgz", 623 | "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==", 624 | "dev": true 625 | }, 626 | "node_modules/@types/web-bluetooth": { 627 | "version": "0.0.17", 628 | "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", 629 | "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==", 630 | "dev": true 631 | }, 632 | "node_modules/@vue/compiler-core": { 633 | "version": "3.3.4", 634 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", 635 | "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", 636 | "dev": true, 637 | "dependencies": { 638 | "@babel/parser": "^7.21.3", 639 | "@vue/shared": "3.3.4", 640 | "estree-walker": "^2.0.2", 641 | "source-map-js": "^1.0.2" 642 | } 643 | }, 644 | "node_modules/@vue/compiler-dom": { 645 | "version": "3.3.4", 646 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", 647 | "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", 648 | "dev": true, 649 | "dependencies": { 650 | "@vue/compiler-core": "3.3.4", 651 | "@vue/shared": "3.3.4" 652 | } 653 | }, 654 | "node_modules/@vue/compiler-sfc": { 655 | "version": "3.3.4", 656 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", 657 | "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", 658 | "dev": true, 659 | "dependencies": { 660 | "@babel/parser": "^7.20.15", 661 | "@vue/compiler-core": "3.3.4", 662 | "@vue/compiler-dom": "3.3.4", 663 | "@vue/compiler-ssr": "3.3.4", 664 | "@vue/reactivity-transform": "3.3.4", 665 | "@vue/shared": "3.3.4", 666 | "estree-walker": "^2.0.2", 667 | "magic-string": "^0.30.0", 668 | "postcss": "^8.1.10", 669 | "source-map-js": "^1.0.2" 670 | } 671 | }, 672 | "node_modules/@vue/compiler-ssr": { 673 | "version": "3.3.4", 674 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", 675 | "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", 676 | "dev": true, 677 | "dependencies": { 678 | "@vue/compiler-dom": "3.3.4", 679 | "@vue/shared": "3.3.4" 680 | } 681 | }, 682 | "node_modules/@vue/devtools-api": { 683 | "version": "6.5.0", 684 | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", 685 | "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==", 686 | "dev": true 687 | }, 688 | "node_modules/@vue/reactivity": { 689 | "version": "3.3.4", 690 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", 691 | "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", 692 | "dev": true, 693 | "dependencies": { 694 | "@vue/shared": "3.3.4" 695 | } 696 | }, 697 | "node_modules/@vue/reactivity-transform": { 698 | "version": "3.3.4", 699 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", 700 | "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", 701 | "dev": true, 702 | "dependencies": { 703 | "@babel/parser": "^7.20.15", 704 | "@vue/compiler-core": "3.3.4", 705 | "@vue/shared": "3.3.4", 706 | "estree-walker": "^2.0.2", 707 | "magic-string": "^0.30.0" 708 | } 709 | }, 710 | "node_modules/@vue/runtime-core": { 711 | "version": "3.3.4", 712 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", 713 | "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", 714 | "dev": true, 715 | "dependencies": { 716 | "@vue/reactivity": "3.3.4", 717 | "@vue/shared": "3.3.4" 718 | } 719 | }, 720 | "node_modules/@vue/runtime-dom": { 721 | "version": "3.3.4", 722 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", 723 | "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", 724 | "dev": true, 725 | "dependencies": { 726 | "@vue/runtime-core": "3.3.4", 727 | "@vue/shared": "3.3.4", 728 | "csstype": "^3.1.1" 729 | } 730 | }, 731 | "node_modules/@vue/server-renderer": { 732 | "version": "3.3.4", 733 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", 734 | "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", 735 | "dev": true, 736 | "dependencies": { 737 | "@vue/compiler-ssr": "3.3.4", 738 | "@vue/shared": "3.3.4" 739 | }, 740 | "peerDependencies": { 741 | "vue": "3.3.4" 742 | } 743 | }, 744 | "node_modules/@vue/shared": { 745 | "version": "3.3.4", 746 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", 747 | "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", 748 | "dev": true 749 | }, 750 | "node_modules/@vueuse/core": { 751 | "version": "10.4.1", 752 | "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz", 753 | "integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==", 754 | "dev": true, 755 | "dependencies": { 756 | "@types/web-bluetooth": "^0.0.17", 757 | "@vueuse/metadata": "10.4.1", 758 | "@vueuse/shared": "10.4.1", 759 | "vue-demi": ">=0.14.5" 760 | }, 761 | "funding": { 762 | "url": "https://github.com/sponsors/antfu" 763 | } 764 | }, 765 | "node_modules/@vueuse/core/node_modules/vue-demi": { 766 | "version": "0.14.6", 767 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 768 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 769 | "dev": true, 770 | "hasInstallScript": true, 771 | "bin": { 772 | "vue-demi-fix": "bin/vue-demi-fix.js", 773 | "vue-demi-switch": "bin/vue-demi-switch.js" 774 | }, 775 | "engines": { 776 | "node": ">=12" 777 | }, 778 | "funding": { 779 | "url": "https://github.com/sponsors/antfu" 780 | }, 781 | "peerDependencies": { 782 | "@vue/composition-api": "^1.0.0-rc.1", 783 | "vue": "^3.0.0-0 || ^2.6.0" 784 | }, 785 | "peerDependenciesMeta": { 786 | "@vue/composition-api": { 787 | "optional": true 788 | } 789 | } 790 | }, 791 | "node_modules/@vueuse/integrations": { 792 | "version": "10.4.1", 793 | "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.4.1.tgz", 794 | "integrity": "sha512-uRBPyG5Lxoh1A/J+boiioPT3ELEAPEo4t8W6Mr4yTKIQBeW/FcbsotZNPr4k9uz+3QEksMmflWloS9wCnypM7g==", 795 | "dev": true, 796 | "dependencies": { 797 | "@vueuse/core": "10.4.1", 798 | "@vueuse/shared": "10.4.1", 799 | "vue-demi": ">=0.14.5" 800 | }, 801 | "funding": { 802 | "url": "https://github.com/sponsors/antfu" 803 | }, 804 | "peerDependencies": { 805 | "async-validator": "*", 806 | "axios": "*", 807 | "change-case": "*", 808 | "drauu": "*", 809 | "focus-trap": "*", 810 | "fuse.js": "*", 811 | "idb-keyval": "*", 812 | "jwt-decode": "*", 813 | "nprogress": "*", 814 | "qrcode": "*", 815 | "sortablejs": "*", 816 | "universal-cookie": "*" 817 | }, 818 | "peerDependenciesMeta": { 819 | "async-validator": { 820 | "optional": true 821 | }, 822 | "axios": { 823 | "optional": true 824 | }, 825 | "change-case": { 826 | "optional": true 827 | }, 828 | "drauu": { 829 | "optional": true 830 | }, 831 | "focus-trap": { 832 | "optional": true 833 | }, 834 | "fuse.js": { 835 | "optional": true 836 | }, 837 | "idb-keyval": { 838 | "optional": true 839 | }, 840 | "jwt-decode": { 841 | "optional": true 842 | }, 843 | "nprogress": { 844 | "optional": true 845 | }, 846 | "qrcode": { 847 | "optional": true 848 | }, 849 | "sortablejs": { 850 | "optional": true 851 | }, 852 | "universal-cookie": { 853 | "optional": true 854 | } 855 | } 856 | }, 857 | "node_modules/@vueuse/integrations/node_modules/vue-demi": { 858 | "version": "0.14.6", 859 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 860 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 861 | "dev": true, 862 | "hasInstallScript": true, 863 | "bin": { 864 | "vue-demi-fix": "bin/vue-demi-fix.js", 865 | "vue-demi-switch": "bin/vue-demi-switch.js" 866 | }, 867 | "engines": { 868 | "node": ">=12" 869 | }, 870 | "funding": { 871 | "url": "https://github.com/sponsors/antfu" 872 | }, 873 | "peerDependencies": { 874 | "@vue/composition-api": "^1.0.0-rc.1", 875 | "vue": "^3.0.0-0 || ^2.6.0" 876 | }, 877 | "peerDependenciesMeta": { 878 | "@vue/composition-api": { 879 | "optional": true 880 | } 881 | } 882 | }, 883 | "node_modules/@vueuse/metadata": { 884 | "version": "10.4.1", 885 | "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz", 886 | "integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==", 887 | "dev": true, 888 | "funding": { 889 | "url": "https://github.com/sponsors/antfu" 890 | } 891 | }, 892 | "node_modules/@vueuse/shared": { 893 | "version": "10.4.1", 894 | "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz", 895 | "integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==", 896 | "dev": true, 897 | "dependencies": { 898 | "vue-demi": ">=0.14.5" 899 | }, 900 | "funding": { 901 | "url": "https://github.com/sponsors/antfu" 902 | } 903 | }, 904 | "node_modules/@vueuse/shared/node_modules/vue-demi": { 905 | "version": "0.14.6", 906 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 907 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 908 | "dev": true, 909 | "hasInstallScript": true, 910 | "bin": { 911 | "vue-demi-fix": "bin/vue-demi-fix.js", 912 | "vue-demi-switch": "bin/vue-demi-switch.js" 913 | }, 914 | "engines": { 915 | "node": ">=12" 916 | }, 917 | "funding": { 918 | "url": "https://github.com/sponsors/antfu" 919 | }, 920 | "peerDependencies": { 921 | "@vue/composition-api": "^1.0.0-rc.1", 922 | "vue": "^3.0.0-0 || ^2.6.0" 923 | }, 924 | "peerDependenciesMeta": { 925 | "@vue/composition-api": { 926 | "optional": true 927 | } 928 | } 929 | }, 930 | "node_modules/algoliasearch": { 931 | "version": "4.19.1", 932 | "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.19.1.tgz", 933 | "integrity": "sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==", 934 | "dev": true, 935 | "dependencies": { 936 | "@algolia/cache-browser-local-storage": "4.19.1", 937 | "@algolia/cache-common": "4.19.1", 938 | "@algolia/cache-in-memory": "4.19.1", 939 | "@algolia/client-account": "4.19.1", 940 | "@algolia/client-analytics": "4.19.1", 941 | "@algolia/client-common": "4.19.1", 942 | "@algolia/client-personalization": "4.19.1", 943 | "@algolia/client-search": "4.19.1", 944 | "@algolia/logger-common": "4.19.1", 945 | "@algolia/logger-console": "4.19.1", 946 | "@algolia/requester-browser-xhr": "4.19.1", 947 | "@algolia/requester-common": "4.19.1", 948 | "@algolia/requester-node-http": "4.19.1", 949 | "@algolia/transporter": "4.19.1" 950 | } 951 | }, 952 | "node_modules/ansi-sequence-parser": { 953 | "version": "1.1.1", 954 | "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", 955 | "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", 956 | "dev": true 957 | }, 958 | "node_modules/csstype": { 959 | "version": "3.1.2", 960 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", 961 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", 962 | "dev": true 963 | }, 964 | "node_modules/esbuild": { 965 | "version": "0.18.20", 966 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 967 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 968 | "dev": true, 969 | "hasInstallScript": true, 970 | "bin": { 971 | "esbuild": "bin/esbuild" 972 | }, 973 | "engines": { 974 | "node": ">=12" 975 | }, 976 | "optionalDependencies": { 977 | "@esbuild/android-arm": "0.18.20", 978 | "@esbuild/android-arm64": "0.18.20", 979 | "@esbuild/android-x64": "0.18.20", 980 | "@esbuild/darwin-arm64": "0.18.20", 981 | "@esbuild/darwin-x64": "0.18.20", 982 | "@esbuild/freebsd-arm64": "0.18.20", 983 | "@esbuild/freebsd-x64": "0.18.20", 984 | "@esbuild/linux-arm": "0.18.20", 985 | "@esbuild/linux-arm64": "0.18.20", 986 | "@esbuild/linux-ia32": "0.18.20", 987 | "@esbuild/linux-loong64": "0.18.20", 988 | "@esbuild/linux-mips64el": "0.18.20", 989 | "@esbuild/linux-ppc64": "0.18.20", 990 | "@esbuild/linux-riscv64": "0.18.20", 991 | "@esbuild/linux-s390x": "0.18.20", 992 | "@esbuild/linux-x64": "0.18.20", 993 | "@esbuild/netbsd-x64": "0.18.20", 994 | "@esbuild/openbsd-x64": "0.18.20", 995 | "@esbuild/sunos-x64": "0.18.20", 996 | "@esbuild/win32-arm64": "0.18.20", 997 | "@esbuild/win32-ia32": "0.18.20", 998 | "@esbuild/win32-x64": "0.18.20" 999 | } 1000 | }, 1001 | "node_modules/estree-walker": { 1002 | "version": "2.0.2", 1003 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1004 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1005 | "dev": true 1006 | }, 1007 | "node_modules/focus-trap": { 1008 | "version": "7.5.2", 1009 | "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", 1010 | "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", 1011 | "dev": true, 1012 | "dependencies": { 1013 | "tabbable": "^6.2.0" 1014 | } 1015 | }, 1016 | "node_modules/fsevents": { 1017 | "version": "2.3.3", 1018 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1019 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1020 | "dev": true, 1021 | "hasInstallScript": true, 1022 | "optional": true, 1023 | "os": [ 1024 | "darwin" 1025 | ], 1026 | "engines": { 1027 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1028 | } 1029 | }, 1030 | "node_modules/jsonc-parser": { 1031 | "version": "3.2.0", 1032 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", 1033 | "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", 1034 | "dev": true 1035 | }, 1036 | "node_modules/magic-string": { 1037 | "version": "0.30.0", 1038 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", 1039 | "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", 1040 | "dev": true, 1041 | "dependencies": { 1042 | "@jridgewell/sourcemap-codec": "^1.4.13" 1043 | }, 1044 | "engines": { 1045 | "node": ">=12" 1046 | } 1047 | }, 1048 | "node_modules/mark.js": { 1049 | "version": "8.11.1", 1050 | "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", 1051 | "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", 1052 | "dev": true 1053 | }, 1054 | "node_modules/minisearch": { 1055 | "version": "6.1.0", 1056 | "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.1.0.tgz", 1057 | "integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==", 1058 | "dev": true 1059 | }, 1060 | "node_modules/nanoid": { 1061 | "version": "3.3.6", 1062 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 1063 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 1064 | "dev": true, 1065 | "funding": [ 1066 | { 1067 | "type": "github", 1068 | "url": "https://github.com/sponsors/ai" 1069 | } 1070 | ], 1071 | "bin": { 1072 | "nanoid": "bin/nanoid.cjs" 1073 | }, 1074 | "engines": { 1075 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1076 | } 1077 | }, 1078 | "node_modules/picocolors": { 1079 | "version": "1.0.0", 1080 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1081 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1082 | "dev": true 1083 | }, 1084 | "node_modules/postcss": { 1085 | "version": "8.4.31", 1086 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 1087 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 1088 | "dev": true, 1089 | "funding": [ 1090 | { 1091 | "type": "opencollective", 1092 | "url": "https://opencollective.com/postcss/" 1093 | }, 1094 | { 1095 | "type": "tidelift", 1096 | "url": "https://tidelift.com/funding/github/npm/postcss" 1097 | }, 1098 | { 1099 | "type": "github", 1100 | "url": "https://github.com/sponsors/ai" 1101 | } 1102 | ], 1103 | "dependencies": { 1104 | "nanoid": "^3.3.6", 1105 | "picocolors": "^1.0.0", 1106 | "source-map-js": "^1.0.2" 1107 | }, 1108 | "engines": { 1109 | "node": "^10 || ^12 || >=14" 1110 | } 1111 | }, 1112 | "node_modules/preact": { 1113 | "version": "10.17.1", 1114 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.17.1.tgz", 1115 | "integrity": "sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==", 1116 | "dev": true, 1117 | "funding": { 1118 | "type": "opencollective", 1119 | "url": "https://opencollective.com/preact" 1120 | } 1121 | }, 1122 | "node_modules/rollup": { 1123 | "version": "3.28.1", 1124 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", 1125 | "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", 1126 | "dev": true, 1127 | "bin": { 1128 | "rollup": "dist/bin/rollup" 1129 | }, 1130 | "engines": { 1131 | "node": ">=14.18.0", 1132 | "npm": ">=8.0.0" 1133 | }, 1134 | "optionalDependencies": { 1135 | "fsevents": "~2.3.2" 1136 | } 1137 | }, 1138 | "node_modules/search-insights": { 1139 | "version": "2.8.1", 1140 | "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.8.1.tgz", 1141 | "integrity": "sha512-gxfqTdzjOxl/i5LtTvSFdolgnm3pUQD5Ae3V8N6tFQ2bsYeo4C3CmrQAsMt212ZV78P22XBUH/nM9IhcAI946Q==", 1142 | "dev": true, 1143 | "peer": true 1144 | }, 1145 | "node_modules/shiki": { 1146 | "version": "0.14.4", 1147 | "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz", 1148 | "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", 1149 | "dev": true, 1150 | "dependencies": { 1151 | "ansi-sequence-parser": "^1.1.0", 1152 | "jsonc-parser": "^3.2.0", 1153 | "vscode-oniguruma": "^1.7.0", 1154 | "vscode-textmate": "^8.0.0" 1155 | } 1156 | }, 1157 | "node_modules/source-map-js": { 1158 | "version": "1.0.2", 1159 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1160 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1161 | "dev": true, 1162 | "engines": { 1163 | "node": ">=0.10.0" 1164 | } 1165 | }, 1166 | "node_modules/tabbable": { 1167 | "version": "6.2.0", 1168 | "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", 1169 | "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", 1170 | "dev": true 1171 | }, 1172 | "node_modules/vite": { 1173 | "version": "4.4.9", 1174 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 1175 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 1176 | "dev": true, 1177 | "dependencies": { 1178 | "esbuild": "^0.18.10", 1179 | "postcss": "^8.4.27", 1180 | "rollup": "^3.27.1" 1181 | }, 1182 | "bin": { 1183 | "vite": "bin/vite.js" 1184 | }, 1185 | "engines": { 1186 | "node": "^14.18.0 || >=16.0.0" 1187 | }, 1188 | "funding": { 1189 | "url": "https://github.com/vitejs/vite?sponsor=1" 1190 | }, 1191 | "optionalDependencies": { 1192 | "fsevents": "~2.3.2" 1193 | }, 1194 | "peerDependencies": { 1195 | "@types/node": ">= 14", 1196 | "less": "*", 1197 | "lightningcss": "^1.21.0", 1198 | "sass": "*", 1199 | "stylus": "*", 1200 | "sugarss": "*", 1201 | "terser": "^5.4.0" 1202 | }, 1203 | "peerDependenciesMeta": { 1204 | "@types/node": { 1205 | "optional": true 1206 | }, 1207 | "less": { 1208 | "optional": true 1209 | }, 1210 | "lightningcss": { 1211 | "optional": true 1212 | }, 1213 | "sass": { 1214 | "optional": true 1215 | }, 1216 | "stylus": { 1217 | "optional": true 1218 | }, 1219 | "sugarss": { 1220 | "optional": true 1221 | }, 1222 | "terser": { 1223 | "optional": true 1224 | } 1225 | } 1226 | }, 1227 | "node_modules/vitepress": { 1228 | "version": "1.0.0-rc.20", 1229 | "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.20.tgz", 1230 | "integrity": "sha512-CykMUJ8JLxLcGWek0ew3wln4RYbsOd1+0YzXITTpajggpynm2S331TNkJVOkHrMRc6GYe3y4pS40GfgcW0ZwAw==", 1231 | "dev": true, 1232 | "dependencies": { 1233 | "@docsearch/css": "^3.5.2", 1234 | "@docsearch/js": "^3.5.2", 1235 | "@types/markdown-it": "^13.0.1", 1236 | "@vue/devtools-api": "^6.5.0", 1237 | "@vueuse/core": "^10.4.1", 1238 | "@vueuse/integrations": "^10.4.1", 1239 | "focus-trap": "^7.5.2", 1240 | "mark.js": "8.11.1", 1241 | "minisearch": "^6.1.0", 1242 | "shiki": "^0.14.4", 1243 | "vite": "^4.4.9", 1244 | "vue": "^3.3.4" 1245 | }, 1246 | "bin": { 1247 | "vitepress": "bin/vitepress.js" 1248 | }, 1249 | "peerDependencies": { 1250 | "markdown-it-mathjax3": "^4.3.2", 1251 | "postcss": "^8.4.30" 1252 | }, 1253 | "peerDependenciesMeta": { 1254 | "markdown-it-mathjax3": { 1255 | "optional": true 1256 | }, 1257 | "postcss": { 1258 | "optional": true 1259 | } 1260 | } 1261 | }, 1262 | "node_modules/vscode-oniguruma": { 1263 | "version": "1.7.0", 1264 | "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", 1265 | "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", 1266 | "dev": true 1267 | }, 1268 | "node_modules/vscode-textmate": { 1269 | "version": "8.0.0", 1270 | "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", 1271 | "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", 1272 | "dev": true 1273 | }, 1274 | "node_modules/vue": { 1275 | "version": "3.3.4", 1276 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", 1277 | "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", 1278 | "dev": true, 1279 | "dependencies": { 1280 | "@vue/compiler-dom": "3.3.4", 1281 | "@vue/compiler-sfc": "3.3.4", 1282 | "@vue/runtime-dom": "3.3.4", 1283 | "@vue/server-renderer": "3.3.4", 1284 | "@vue/shared": "3.3.4" 1285 | } 1286 | } 1287 | } 1288 | } 1289 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "devDependencies": { 4 | "vitepress": "^1.0.0-rc.20", 5 | "vue": "^3.3.4" 6 | }, 7 | "scripts": { 8 | "docs:dev": "vitepress dev docs", 9 | "docs:build": "vitepress build docs", 10 | "docs:preview": "vitepress preview docs" 11 | } 12 | } -------------------------------------------------------------------------------- /wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "red-blox/red" 3 | version = "2.3.1" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | license = "MIT" 7 | exclude = ["**"] 8 | include = [ 9 | "default.project.json", 10 | "lib", 11 | "lib/**", 12 | "LICENSE", 13 | "wally.toml", 14 | "README.md", 15 | ] 16 | 17 | [dependencies] 18 | Future = "red-blox/future@^1.0.0" 19 | Signal = "red-blox/signal@^2.0.0" 20 | Spawn = "red-blox/spawn@^1.0.0" 21 | Guard = "red-blox/guard@^1.0.0" 22 | --------------------------------------------------------------------------------