├── .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 |

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 |
--------------------------------------------------------------------------------