@rbxts/tether 1.1.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Tether
2
- A message-based networking solution for Roblox with automatic binary serialization.
2
+ A message-based networking solution for Roblox with automatic binary serialization and type validation.
3
3
 
4
4
  > [!CAUTION]
5
5
  > Depends on `rbxts-transformer-flamework`!
@@ -14,7 +14,8 @@ import { MessageEmitter } from "@rbxts/tether";
14
14
  export const messaging = MessageEmitter.create<MessageData>();
15
15
 
16
16
  export const enum Message {
17
- Test
17
+ Test,
18
+ Packed
18
19
  }
19
20
 
20
21
  export interface MessageData {
@@ -22,6 +23,16 @@ export interface MessageData {
22
23
  readonly foo: string;
23
24
  readonly n: DataType.u8;
24
25
  };
26
+ [Message.Packed]: DataType.Packed<{
27
+ boolean1: boolean;
28
+ boolean2: boolean;
29
+ boolean3: boolean;
30
+ boolean4: boolean;
31
+ boolean5: boolean;
32
+ boolean6: boolean;
33
+ boolean7: boolean;
34
+ boolean8: boolean;
35
+ }>;
25
36
  }
26
37
  ```
27
38
 
@@ -32,7 +43,7 @@ export interface MessageData {
32
43
  ```ts
33
44
  import { Message, messaging } from "shared/messaging";
34
45
 
35
- messaging.onServerMessage(Message.Test, (player, data) => {
46
+ messaging.server.on(Message.Test, (player, data) => {
36
47
  print(player, "sent data:", data);
37
48
  });
38
49
  ```
@@ -41,17 +52,65 @@ messaging.onServerMessage(Message.Test, (player, data) => {
41
52
  ```ts
42
53
  import { Message, messaging } from "shared/messaging";
43
54
 
44
- messaging.emitServer(Message.Test, {
55
+ messaging.server.emit(Message.Test, {
45
56
  foo: "bar",
46
57
  n: 69
47
58
  });
48
59
  ```
49
60
 
61
+ ## Simulated Remote Functions
62
+ Tether does not directly use RemoteFunctions since it's based on the MessageEmitter structure. However I have created a small framework to simulate remote functions, as shown below.
63
+
64
+ For each function you will need two messages. One to invoke the function, and one to send the return value back (which is done automatically).
65
+
66
+ ### In `shared/messaging.ts`
67
+ ```ts
68
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
69
+ import { MessageEmitter } from "@rbxts/tether";
70
+
71
+ export const messaging = MessageEmitter.create<MessageData>();
72
+
73
+ export const enum Message {
74
+ Increment,
75
+ IncrementReturn
76
+ }
77
+
78
+ export interface MessageData {
79
+ [Message.Increment]: DataType.u8;
80
+ [Message.IncrementReturn]: DataType.u8;
81
+ }
82
+ ```
83
+
84
+ ### Server
85
+ ```ts
86
+ import { Message, messaging } from "shared/messaging";
87
+
88
+ messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
89
+ ```
90
+
91
+ ### Client
92
+ ```ts
93
+ import { Message, messaging } from "shared/messaging";
94
+
95
+ messaging.server
96
+ .invoke(Message.Increment, Message.IncrementReturn, 69)
97
+ .then(print); // 70 - incremented by the server
98
+
99
+ // or use await style
100
+ async function main(): Promise<void> {
101
+ const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
102
+ print(value) // 70
103
+ }
104
+
105
+ main();
106
+ ```
107
+
50
108
  ## Middleware
51
109
  Drop, delay, or modify requests
52
110
 
53
111
  ### Creating middleware
54
112
 
113
+ **Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
55
114
  #### Client
56
115
  ```ts
57
116
  import type { ClientMiddleware } from "@rbxts/tether";
@@ -106,18 +165,18 @@ export const messaging = MessageEmitter.create<MessageData>();
106
165
  messaging.middleware
107
166
  // only allows requests to the server every 5 seconds,
108
167
  // drops any requests that occur within 5 seconds of each other
109
- .useServer(Message.Test, [BuiltinMiddlewares.rateLimit(5)])
110
- // automatically validates that the data sent through the remote matches
111
- // the data associated with the message at runtime using type guards
112
- .useServer(Message.Test, [BuiltinMiddlewares.validateServer()])
168
+ .useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
169
+ .useShared(Message.Packed, () => (_, __, getRawData) => print("Packed object size:", buffer.len(getRawData()))); // will be just one byte!
113
170
  // logs every message fired
114
- .useServerGlobal([logServer()])
115
- .useClientGlobal([logClient()])
116
- .useServer(Message.Test, [incrementNumberData()]) // error! - data for Message.Test is not a number
117
- .useServerGlobal([incrementNumberData()]); // error! - global data type is always 'unknown', we cannot guarantee a number
171
+ .useServerGlobal(logServer())
172
+ .useClientGlobal(logClient())
173
+ .useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
174
+ .useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
175
+ .useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
118
176
 
119
177
  export const enum Message {
120
- Test
178
+ Test,
179
+ Packed
121
180
  }
122
181
 
123
182
  export interface MessageData {
@@ -125,5 +184,15 @@ export interface MessageData {
125
184
  readonly foo: string;
126
185
  readonly n: DataType.u8;
127
186
  };
187
+ [Message.Packed]: DataType.Packed<{
188
+ boolean1: boolean;
189
+ boolean2: boolean;
190
+ boolean3: boolean;
191
+ boolean4: boolean;
192
+ boolean5: boolean;
193
+ boolean6: boolean;
194
+ boolean7: boolean;
195
+ boolean8: boolean;
196
+ }>;
128
197
  }
129
198
  ```
@@ -1,33 +1,35 @@
1
1
  import { Modding } from "@flamework/core";
2
- import { type SharedMiddleware, type ServerMiddleware, type ClientMiddleware } from "./middleware";
3
- type Guard<T> = (value: unknown) => value is T;
2
+ import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
+ import { type SharedMiddleware } from "./middleware";
4
+ import type { TetherPacket } from "./structs";
5
+ import { Any } from "ts-toolbelt";
4
6
  export declare namespace BuiltinMiddlewares {
5
7
  /**
6
- * Creates a shared per-message middleware that will drop any message that occurs within the given interval of the previous message
8
+ * Creates a shared middleware that will simulate a ping of the given amount when a message is sent
7
9
  *
8
- * @param interval The interval in seconds that the middleware should wait before allowing a new request
9
- * @returns A middleware that will drop any message that occurs within the given interval
10
+ * @param pingInMs The amount of time in milliseconds that the middleware should wait
11
+ * @returns A shared middleware that will simulate a ping
10
12
  */
11
- function rateLimit(interval: number): SharedMiddleware;
13
+ function simulatePing(pingInMs: number): SharedMiddleware;
12
14
  /**
13
- * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided)
15
+ * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
14
16
  *
15
- * If the guard fails, the middleware will drop the message
16
- *
17
- * @param guard The guard to use to validate the data
18
- * @returns A server middleware that validates the data with the given guard
19
- * @metadata macro
17
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request
18
+ * @returns A middleware that will drop any message that occurs within the given interval
20
19
  */
21
- function validateServer<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ServerMiddleware<T>;
20
+ function rateLimit(interval: number): SharedMiddleware;
22
21
  /**
23
- * Creates a client middleware that validates the data with the given guard (or a generated guard if none was provided)
24
- *
25
- * If the guard fails, the middleware will drop the message
22
+ * Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
23
+ * - The message kind
24
+ * - The data associated with the message
25
+ * - The raw data (buffer and blobs) associated with the message
26
+ * - The size of the packet (in bytes)
27
+ * - The size of the buffer (in bytes)
28
+ * - The size of the blobs (in bytes)
29
+ * - The schema string associated with the message (if it can be deduced)
26
30
  *
27
- * @param guard The guard to use to validate the data.
28
- * @returns A client middleware that validates the data with the given guard.
31
+ * @returns A shared middleware that will log a message whenever a message is sent.
29
32
  * @metadata macro
30
33
  */
31
- function validateClient<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ClientMiddleware<T>;
34
+ function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<TetherPacket<T>>>): SharedMiddleware<T>;
32
35
  }
33
- export {};
@@ -1,21 +1,32 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
+ local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
4
+ local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
3
5
  local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
4
- local noOp = function()
5
- return function() end
6
- end
7
- local validationGuardGenerationFailed = function(context)
8
- return `[@rbxts/tether]: Failed to generate guard for validate{context}<T> builtin middleware - skipping validation`
9
- end
10
- local guardFailed = function(message)
11
- return `[@rbxts/tether]: Type validation failed for message '{tostring(message)}' - check your sent data`
12
- end
6
+ local BLOB_SIZE = 5
13
7
  local BuiltinMiddlewares = {}
14
8
  do
15
9
  local _container = BuiltinMiddlewares
16
10
  --[[
17
11
  *
18
- * Creates a shared per-message middleware that will drop any message that occurs within the given interval of the previous message
12
+ * Creates a shared middleware that will simulate a ping of the given amount when a message is sent
13
+ *
14
+ * @param pingInMs The amount of time in milliseconds that the middleware should wait
15
+ * @returns A shared middleware that will simulate a ping
16
+
17
+ ]]
18
+ local function simulatePing(pingInMs)
19
+ return function()
20
+ return function()
21
+ task.wait(pingInMs / 1000)
22
+ return nil
23
+ end
24
+ end
25
+ end
26
+ _container.simulatePing = simulatePing
27
+ --[[
28
+ *
29
+ * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
19
30
  *
20
31
  * @param interval The interval in seconds that the middleware should wait before allowing a new request
21
32
  * @returns A middleware that will drop any message that occurs within the given interval
@@ -33,60 +44,71 @@ do
33
44
  end
34
45
  end
35
46
  _container.rateLimit = rateLimit
36
- --[[
37
- *
38
- * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided)
39
- *
40
- * If the guard fails, the middleware will drop the message
41
- *
42
- * @param guard The guard to use to validate the data
43
- * @returns A server middleware that validates the data with the given guard
44
- * @metadata macro
45
-
46
- ]]
47
- local function validateServer(guard)
48
- if guard == nil then
49
- warn(validationGuardGenerationFailed("Server"))
50
- return noOp
51
- end
52
- return function(message)
53
- return function(data)
54
- if guard(data) then
55
- return nil
47
+ local function bufferToString(buf)
48
+ local s = { "{ " }
49
+ do
50
+ local i = 0
51
+ local _shouldIncrement = false
52
+ while true do
53
+ if _shouldIncrement then
54
+ i += 1
55
+ else
56
+ _shouldIncrement = true
57
+ end
58
+ if not (i < buffer.len(buf)) then
59
+ break
60
+ end
61
+ local byte = buffer.readu8(buf, i)
62
+ local _arg0 = tostring(byte)
63
+ table.insert(s, _arg0)
64
+ if i < buffer.len(buf) - 1 then
65
+ table.insert(s, ", ")
56
66
  end
57
- warn(guardFailed(message))
58
- return DropRequest
59
67
  end
60
68
  end
69
+ table.insert(s, " }")
70
+ return table.concat(s, "")
61
71
  end
62
- _container.validateServer = validateServer
72
+ local horizontalLine = "------------------------------------"
63
73
  --[[
64
74
  *
65
- * Creates a client middleware that validates the data with the given guard (or a generated guard if none was provided)
66
- *
67
- * If the guard fails, the middleware will drop the message
75
+ * Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
76
+ * - The message kind
77
+ * - The data associated with the message
78
+ * - The raw data (buffer and blobs) associated with the message
79
+ * - The size of the packet (in bytes)
80
+ * - The size of the buffer (in bytes)
81
+ * - The size of the blobs (in bytes)
82
+ * - The schema string associated with the message (if it can be deduced)
68
83
  *
69
- * @param guard The guard to use to validate the data.
70
- * @returns A client middleware that validates the data with the given guard.
84
+ * @returns A shared middleware that will log a message whenever a message is sent.
71
85
  * @metadata macro
72
86
 
73
87
  ]]
74
- local function validateClient(guard)
75
- if guard == nil then
76
- warn(validationGuardGenerationFailed("Client"))
77
- return noOp
78
- end
88
+ local function debug(schema)
79
89
  return function(message)
80
- return function(player, data)
81
- if guard(data) then
82
- return nil
90
+ return function(data, _, getRawData)
91
+ local rawData = getRawData()
92
+ local bufferSize = buffer.len(rawData.buffer)
93
+ local blobsSize = #rawData.blobs * BLOB_SIZE
94
+ local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[2], {
95
+ pretty = true,
96
+ }), "\n"), "\n ") else "unknown"
97
+ local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", message, "\n", " - Data: ", if data == nil then "undefined" else data, "\n", " - Raw data:\n", " - Buffer: ", bufferToString(rawData.buffer), "\n", " - Blobs: ", repr(rawData.blobs, {
98
+ pretty = false,
99
+ robloxClassName = true,
100
+ }), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
101
+ -- ▼ ReadonlyArray.join ▼
102
+ local _result = table.create(#text)
103
+ for _k, _v in text do
104
+ _result[_k] = tostring(_v)
83
105
  end
84
- warn(guardFailed(message))
85
- return DropRequest
106
+ -- ▲ ReadonlyArray.join ▲
107
+ print(table.concat(_result, ""))
86
108
  end
87
109
  end
88
110
  end
89
- _container.validateClient = validateClient
111
+ _container.debug = debug
90
112
  end
91
113
  return {
92
114
  BuiltinMiddlewares = BuiltinMiddlewares,
@@ -1,54 +1,103 @@
1
1
  import { Modding } from "@flamework/core";
2
- import { type SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
2
  import Destroyable from "@rbxts/destroyable";
4
3
  import { MiddlewareProvider } from "./middleware";
5
- import type { TetherPacket, ClientMessageCallback, ServerMessageCallback, BaseMessage } from "./structs";
4
+ import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata, ClientMessageFunctionCallback, ServerMessageFunctionCallback } from "./structs";
6
5
  export declare class MessageEmitter<MessageData> extends Destroyable {
7
6
  readonly middleware: MiddlewareProvider<MessageData>;
8
7
  private readonly clientCallbacks;
8
+ private readonly clientFunctions;
9
9
  private readonly serverCallbacks;
10
+ private readonly serverFunctions;
11
+ private readonly guards;
10
12
  private serializers;
11
13
  private serverEvents;
12
14
  private clientEvents;
13
15
  /** @metadata macro */
14
- static create<MessageData>(metaForEachMessage?: Modding.Many<{
15
- [Kind in keyof MessageData]: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<TetherPacket<MessageData[Kind]>>>;
16
- }>): MessageEmitter<MessageData>;
16
+ static create<MessageData>(meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
17
17
  private constructor();
18
- /**.
19
- * @returns A destructor function that disconnects the callback from the message
20
- */
21
- onServerMessage<Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>): () => void;
22
- /**.
23
- * @returns A destructor function that disconnects the callback from the message
24
- */
25
- onClientMessage<Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>): () => void;
26
- /**
27
- * Emits a message to all connected clients
28
- *
29
- * @param message The message kind to be sent
30
- * @param data The data associated with the message
31
- * @param unreliable Whether the message should be sent unreliably
32
- */
33
- emitServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean): void;
34
- /**
35
- * Emits a message to a specific client
36
- *
37
- * @param player The player to whom the message is sent
38
- * @param message The message kind to be sent
39
- * @param data The data associated with the message
40
- * @param unreliable Whether the message should be sent unreliably
41
- */
42
- emitClient<Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean): void;
43
- /**
44
- * Emits a message to all connected clients
45
- *
46
- * @param message The message kind to be sent
47
- * @param data The data associated with the message
48
- * @param unreliable Whether the message should be sent unreliably
49
- */
50
- emitAllClients<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean): void;
18
+ readonly server: {
19
+ /**
20
+ * @returns A destructor function that disconnects the callback from the message
21
+ */
22
+ on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
23
+ /**
24
+ * Disconnects the callback as soon as it is called for the first time
25
+ *
26
+ * @returns A destructor function that disconnects the callback from the message
27
+ */
28
+ once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ServerMessageCallback<MessageData[Kind]>) => () => void;
29
+ /**
30
+ * Emits a message to the server
31
+ *
32
+ * @param message The message kind to be sent
33
+ * @param data The data associated with the message
34
+ * @param unreliable Whether the message should be sent unreliably
35
+ */
36
+ emit: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
37
+ /**
38
+ * Simulates a remote function invocation.
39
+ *
40
+ * @param message The message kind to be sent
41
+ * @param data The data associated with the message
42
+ * @param unreliable Whether the message should be sent unreliably
43
+ */
44
+ invoke: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => Promise<MessageData[ReturnKind]>;
45
+ /**
46
+ * Sets a callback for a simulated remote function
47
+ *
48
+ * @returns A destructor function that disconnects the callback from the message
49
+ */
50
+ setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ServerMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
51
+ };
52
+ readonly client: {
53
+ /**
54
+ * @returns A destructor function that disconnects the callback from the message
55
+ */
56
+ on: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
57
+ /**
58
+ * Disconnects the callback as soon as it is called for the first time
59
+ *
60
+ * @returns A destructor function that disconnects the callback from the message
61
+ */
62
+ once: <Kind extends keyof MessageData>(message: Kind & BaseMessage, callback: ClientMessageCallback<MessageData[Kind]>) => () => void;
63
+ /**
64
+ * Emits a message to a specific client
65
+ *
66
+ * @param player The player to whom the message is sent
67
+ * @param message The message kind to be sent
68
+ * @param data The data associated with the message
69
+ * @param unreliable Whether the message should be sent unreliably
70
+ */
71
+ emit: <Kind extends keyof MessageData>(player: Player | Player[], message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
72
+ /**
73
+ * Emits a message to all connected clients
74
+ *
75
+ * @param message The message kind to be sent
76
+ * @param data The data associated with the message
77
+ * @param unreliable Whether the message should be sent unreliably
78
+ */
79
+ emitAll: <Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], unreliable?: boolean) => void;
80
+ /**
81
+ * Simulates a remote function invocation.
82
+ *
83
+ * @param message The message kind to be sent
84
+ * @param data The data associated with the message
85
+ * @param unreliable Whether the message should be sent unreliably
86
+ */
87
+ invoke: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, player: Player, data?: MessageData[Kind], unreliable?: boolean) => Promise<MessageData[ReturnKind]>;
88
+ /**
89
+ * Sets a callback for a simulated remote function
90
+ *
91
+ * @returns A destructor function that disconnects the callback from the message
92
+ */
93
+ setCallback: <Kind extends keyof MessageData, ReturnKind extends keyof MessageData>(message: Kind & BaseMessage, returnMessage: ReturnKind & BaseMessage, callback: ClientMessageFunctionCallback<MessageData[Kind], MessageData[ReturnKind]>) => () => void;
94
+ };
95
+ private validateData;
51
96
  private initialize;
97
+ private readMessageFromPacket;
98
+ private executeFunctions;
99
+ private executeEventCallbacks;
100
+ private once;
52
101
  private on;
53
102
  private getPacket;
54
103
  /** @metadata macro */
@@ -10,7 +10,12 @@ local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyabl
10
10
  local _middleware = TS.import(script, script.Parent, "middleware")
11
11
  local DropRequest = _middleware.DropRequest
12
12
  local MiddlewareProvider = _middleware.MiddlewareProvider
13
- local GlobalEvents = Networking.createEvent("@rbxts/tether:message-emitter@GlobalEvents")
13
+ -- TODO: error when trying to do something like server.emit() from the server
14
+ local remotes = Networking.createEvent("@rbxts/tether:message-emitter@remotes")
15
+ local metaGenerationFailed = "[@rbxts/tether]: Failed to generate message metadata - make sure you are using Flamework macro-friendly types in your schemas"
16
+ local guardFailed = function(message)
17
+ return `[@rbxts/tether]: Type validation guard failed for message '{message}' - check your sent data`
18
+ end
14
19
  local MessageEmitter
15
20
  do
16
21
  local super = Destroyable
@@ -29,8 +34,213 @@ do
29
34
  super.constructor(self)
30
35
  self.middleware = MiddlewareProvider.new()
31
36
  self.clientCallbacks = {}
37
+ self.clientFunctions = {}
32
38
  self.serverCallbacks = {}
39
+ self.serverFunctions = {}
40
+ self.guards = {}
33
41
  self.serializers = {}
42
+ self.server = {
43
+ on = function(message, callback)
44
+ return self:on(message, callback, self.serverCallbacks)
45
+ end,
46
+ once = function(message, callback)
47
+ return self:once(message, callback, self.serverCallbacks)
48
+ end,
49
+ emit = function(message, data, unreliable)
50
+ if unreliable == nil then
51
+ unreliable = false
52
+ end
53
+ local updateData = function(newData)
54
+ data = newData
55
+ return nil
56
+ end
57
+ local getPacket = function()
58
+ return self:getPacket(message, data)
59
+ end
60
+ if not self:validateData(message, data) then
61
+ return nil
62
+ end
63
+ task.spawn(function()
64
+ for _, globalMiddleware in self.middleware:getServerGlobal() do
65
+ if not self:validateData(message, data) then
66
+ return nil
67
+ end
68
+ local result = globalMiddleware(message)(data, updateData, getPacket)
69
+ if result == DropRequest then
70
+ return nil
71
+ end
72
+ end
73
+ for _, middleware in self.middleware:getServer(message) do
74
+ if not self:validateData(message, data) then
75
+ return nil
76
+ end
77
+ local result = middleware(message)(data, updateData, getPacket)
78
+ if result == DropRequest then
79
+ return nil
80
+ end
81
+ end
82
+ if not self:validateData(message, data) then
83
+ return nil
84
+ end
85
+ local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
86
+ send(getPacket())
87
+ end)
88
+ end,
89
+ invoke = TS.async(function(message, returnMessage, data, unreliable)
90
+ if unreliable == nil then
91
+ unreliable = false
92
+ end
93
+ local _clientFunctions = self.clientFunctions
94
+ local _returnMessage = returnMessage
95
+ if not (_clientFunctions[_returnMessage] ~= nil) then
96
+ local _clientFunctions_1 = self.clientFunctions
97
+ local _returnMessage_1 = returnMessage
98
+ _clientFunctions_1[_returnMessage_1] = {}
99
+ end
100
+ local _clientFunctions_1 = self.clientFunctions
101
+ local _returnMessage_1 = returnMessage
102
+ local functions = _clientFunctions_1[_returnMessage_1]
103
+ local returnValue
104
+ functions[function(data)
105
+ returnValue = data
106
+ return returnValue
107
+ end] = true
108
+ self.server.emit(message, data, unreliable)
109
+ while returnValue == nil do
110
+ RunService.Heartbeat:Wait()
111
+ end
112
+ return returnValue
113
+ end),
114
+ setCallback = function(message, returnMessage, callback)
115
+ return self.server.on(message, function(player, data)
116
+ local returnValue = callback(player, data)
117
+ self.client.emit(player, returnMessage, returnValue)
118
+ end)
119
+ end,
120
+ }
121
+ self.client = {
122
+ on = function(message, callback)
123
+ return self:on(message, callback, self.clientCallbacks)
124
+ end,
125
+ once = function(message, callback)
126
+ return self:once(message, callback, self.clientCallbacks)
127
+ end,
128
+ emit = function(player, message, data, unreliable)
129
+ if unreliable == nil then
130
+ unreliable = false
131
+ end
132
+ local updateData = function(newData)
133
+ data = newData
134
+ return nil
135
+ end
136
+ local getPacket = function()
137
+ return self:getPacket(message, data)
138
+ end
139
+ if not self:validateData(message, data) then
140
+ return nil
141
+ end
142
+ task.spawn(function()
143
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
144
+ if not self:validateData(message, data) then
145
+ return nil
146
+ end
147
+ local result = globalMiddleware(message)(player, data, updateData, getPacket)
148
+ if result == DropRequest then
149
+ return nil
150
+ end
151
+ end
152
+ for _, middleware in self.middleware:getClient(message) do
153
+ if not self:validateData(message, data) then
154
+ return nil
155
+ end
156
+ local result = middleware(message)(player, data, updateData, getPacket)
157
+ if result == DropRequest then
158
+ return nil
159
+ end
160
+ end
161
+ if not self:validateData(message, data) then
162
+ return nil
163
+ end
164
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
165
+ send(player, getPacket())
166
+ end)
167
+ end,
168
+ emitAll = function(message, data, unreliable)
169
+ if unreliable == nil then
170
+ unreliable = false
171
+ end
172
+ local updateData = function(newData)
173
+ data = newData
174
+ return nil
175
+ end
176
+ local getPacket = function()
177
+ return self:getPacket(message, data)
178
+ end
179
+ if not self:validateData(message, data) then
180
+ return nil
181
+ end
182
+ task.spawn(function()
183
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
184
+ for _1, player in Players:GetPlayers() do
185
+ if not self:validateData(message, data) then
186
+ return nil
187
+ end
188
+ local result = globalMiddleware(message)(player, data, updateData, getPacket)
189
+ if result == DropRequest then
190
+ return nil
191
+ end
192
+ end
193
+ end
194
+ for _, middleware in self.middleware:getClient(message) do
195
+ for _1, player in Players:GetPlayers() do
196
+ if not self:validateData(message, data) then
197
+ return nil
198
+ end
199
+ local result = middleware(message)(player, data, updateData, getPacket)
200
+ if result == DropRequest then
201
+ return nil
202
+ end
203
+ end
204
+ end
205
+ if not self:validateData(message, data) then
206
+ return nil
207
+ end
208
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
209
+ send:broadcast(getPacket())
210
+ end)
211
+ end,
212
+ invoke = TS.async(function(message, returnMessage, player, data, unreliable)
213
+ if unreliable == nil then
214
+ unreliable = false
215
+ end
216
+ local _serverFunctions = self.serverFunctions
217
+ local _returnMessage = returnMessage
218
+ if not (_serverFunctions[_returnMessage] ~= nil) then
219
+ local _serverFunctions_1 = self.serverFunctions
220
+ local _returnMessage_1 = returnMessage
221
+ _serverFunctions_1[_returnMessage_1] = {}
222
+ end
223
+ local _serverFunctions_1 = self.serverFunctions
224
+ local _returnMessage_1 = returnMessage
225
+ local functions = _serverFunctions_1[_returnMessage_1]
226
+ local returnValue
227
+ functions[function(data)
228
+ returnValue = data
229
+ return returnValue
230
+ end] = true
231
+ self.client.emit(player, message, data, unreliable)
232
+ while returnValue == nil do
233
+ RunService.Heartbeat:Wait()
234
+ end
235
+ return returnValue
236
+ end),
237
+ setCallback = function(message, returnMessage, callback)
238
+ return self.client.on(message, function(data)
239
+ local returnValue = callback(data)
240
+ self.server.emit(returnMessage, returnValue)
241
+ end)
242
+ end,
243
+ }
34
244
  self.janitor:Add(function()
35
245
  table.clear(self.clientCallbacks)
36
246
  table.clear(self.serverCallbacks)
@@ -39,7 +249,7 @@ do
39
249
  self.clientEvents = nil
40
250
  end)
41
251
  if RunService:IsServer() then
42
- self.serverEvents = GlobalEvents:createServer({}, {
252
+ self.serverEvents = remotes:createServer({}, {
43
253
  incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
44
254
  incoming = {
45
255
  sendServerMessage = { { t.interface({
@@ -62,7 +272,7 @@ do
62
272
  namespaces = {},
63
273
  })
64
274
  else
65
- self.clientEvents = GlobalEvents:createClient({}, {
275
+ self.clientEvents = remotes:createClient({}, {
66
276
  incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
67
277
  incoming = {
68
278
  sendClientMessage = { { t.interface({
@@ -86,171 +296,130 @@ do
86
296
  })
87
297
  end
88
298
  end
89
- function MessageEmitter:create(metaForEachMessage)
299
+ function MessageEmitter:create(meta)
90
300
  local emitter = MessageEmitter.new()
91
- if metaForEachMessage == nil then
92
- warn("[@rbxts/tether]: Failed to generate serializer metadata for MessageEmitter")
301
+ if meta == nil then
302
+ warn(metaGenerationFailed)
93
303
  return emitter:initialize()
94
304
  end
95
- for kind, meta in pairs(metaForEachMessage) do
96
- if meta == nil then
305
+ for kind, _binding in pairs(meta) do
306
+ local guard = _binding.guard
307
+ local serializerMetadata = _binding.serializerMetadata
308
+ local numberKind = tonumber(kind)
309
+ emitter.guards[numberKind] = guard
310
+ if serializerMetadata == nil then
97
311
  continue
98
312
  end
99
- emitter:addSerializer(tonumber(kind), meta)
313
+ emitter:addSerializer(numberKind, serializerMetadata)
100
314
  end
101
315
  return emitter:initialize()
102
316
  end
103
- function MessageEmitter:onServerMessage(message, callback)
104
- return self:on(message, callback)
105
- end
106
- function MessageEmitter:onClientMessage(message, callback)
107
- return self:on(message, callback)
108
- end
109
- function MessageEmitter:emitServer(message, data, unreliable)
110
- if unreliable == nil then
111
- unreliable = false
112
- end
113
- local updateData = function(newData)
114
- data = newData
115
- return nil
116
- end
117
- for _, globalMiddleware in self.middleware:getServerGlobal() do
118
- local result = globalMiddleware(message)(data, updateData)
119
- if result == DropRequest then
120
- return nil
121
- end
122
- end
123
- for _, middleware in self.middleware:getServer(message) do
124
- local result = middleware(message)(data, updateData)
125
- if result == DropRequest then
126
- return nil
127
- end
317
+ function MessageEmitter:validateData(message, data)
318
+ local _guards = self.guards
319
+ local _message = message
320
+ local guard = _guards[_message]
321
+ local guardPassed = guard(data)
322
+ if not guardPassed then
323
+ warn(guardFailed(message))
128
324
  end
129
- local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
130
- send(self:getPacket(message, data))
325
+ return guardPassed
131
326
  end
132
- function MessageEmitter:emitClient(player, message, data, unreliable)
133
- if unreliable == nil then
134
- unreliable = false
327
+ function MessageEmitter:initialize()
328
+ if RunService:IsClient() then
329
+ self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
330
+ local sentMessage = self:readMessageFromPacket(serializedPacket)
331
+ self:executeEventCallbacks(sentMessage, serializedPacket)
332
+ self:executeFunctions(sentMessage, serializedPacket)
333
+ end))
334
+ else
335
+ self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
336
+ local sentMessage = self:readMessageFromPacket(serializedPacket)
337
+ self:executeEventCallbacks(sentMessage, serializedPacket, player)
338
+ self:executeFunctions(sentMessage, serializedPacket)
339
+ end))
135
340
  end
136
- local updateData = function(newData)
137
- data = newData
341
+ return self
342
+ end
343
+ function MessageEmitter:readMessageFromPacket(serializedPacket)
344
+ return buffer.readu8(serializedPacket.buffer, 0)
345
+ end
346
+ function MessageEmitter:executeFunctions(message, serializedPacket)
347
+ local isServer = RunService:IsServer()
348
+ local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
349
+ local _message = message
350
+ local functions = functionsMap[_message]
351
+ if functions == nil then
138
352
  return nil
139
353
  end
140
- for _, globalMiddleware in self.middleware:getClientGlobal() do
141
- local result = globalMiddleware(message)(player, data, updateData)
142
- if result == DropRequest then
143
- return nil
144
- end
354
+ local serializer = self:getSerializer(message)
355
+ local _packet = serializer
356
+ if _packet ~= nil then
357
+ _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
145
358
  end
146
- for _, middleware in self.middleware:getClient(message) do
147
- local result = middleware(message)(player, data, updateData)
148
- if result == DropRequest then
149
- return nil
359
+ local packet = _packet
360
+ for callback in functions do
361
+ local _result = packet
362
+ if _result ~= nil then
363
+ _result = _result.data
150
364
  end
365
+ callback(_result)
151
366
  end
152
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
153
- send(player, self:getPacket(message, data))
154
367
  end
155
- function MessageEmitter:emitAllClients(message, data, unreliable)
156
- if unreliable == nil then
157
- unreliable = false
158
- end
159
- local updateData = function(newData)
160
- data = newData
368
+ function MessageEmitter:executeEventCallbacks(message, serializedPacket, player)
369
+ local isServer = RunService:IsServer()
370
+ local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks
371
+ local _message = message
372
+ local callbacks = callbacksMap[_message]
373
+ if callbacks == nil then
161
374
  return nil
162
375
  end
163
- for _, globalMiddleware in self.middleware:getClientGlobal() do
164
- for _1, player in Players:GetPlayers() do
165
- local result = globalMiddleware(message)(player, data, updateData)
166
- if result == DropRequest then
167
- return nil
168
- end
169
- end
376
+ local serializer = self:getSerializer(message)
377
+ local _packet = serializer
378
+ if _packet ~= nil then
379
+ _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
170
380
  end
171
- for _, middleware in self.middleware:getClient(message) do
172
- for _1, player in Players:GetPlayers() do
173
- local result = middleware(message)(player, data, updateData)
174
- if result == DropRequest then
175
- return nil
381
+ local packet = _packet
382
+ for callback in callbacks do
383
+ if isServer then
384
+ local _exp = player
385
+ local _result = packet
386
+ if _result ~= nil then
387
+ _result = _result.data
388
+ end
389
+ callback(_exp, _result)
390
+ else
391
+ local _result = packet
392
+ if _result ~= nil then
393
+ _result = _result.data
176
394
  end
395
+ callback(_result)
177
396
  end
178
397
  end
179
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
180
- send:broadcast(self:getPacket(message, data))
181
398
  end
182
- function MessageEmitter:initialize()
183
- if RunService:IsClient() then
184
- self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
185
- local sentMessage = buffer.readu8(serializedPacket.buffer, 0)
186
- local messageCallbacks = self.clientCallbacks[sentMessage] or {}
187
- -- ▼ ReadonlySet.size ▼
188
- local _size = 0
189
- for _ in messageCallbacks do
190
- _size += 1
191
- end
192
- -- ▲ ReadonlySet.size ▲
193
- if _size == 0 then
194
- return nil
195
- end
196
- local serializer = self:getSerializer(sentMessage)
197
- local _packet = serializer
198
- if _packet ~= nil then
199
- _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
200
- end
201
- local packet = _packet
202
- for callback in messageCallbacks do
203
- local _result = packet
204
- if _result ~= nil then
205
- _result = _result.data
206
- end
207
- callback(_result)
208
- end
209
- end))
210
- else
211
- self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
212
- local sentMessage = buffer.readu8(serializedPacket.buffer, 0)
213
- local messageCallbacks = self.serverCallbacks[sentMessage] or {}
214
- -- ▼ ReadonlySet.size ▼
215
- local _size = 0
216
- for _ in messageCallbacks do
217
- _size += 1
218
- end
219
- -- ▲ ReadonlySet.size ▲
220
- if _size == 0 then
221
- return nil
222
- end
223
- local serializer = self:getSerializer(sentMessage)
224
- local _packet = serializer
225
- if _packet ~= nil then
226
- _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
227
- end
228
- local packet = _packet
229
- for callback in messageCallbacks do
230
- local _exp = player
231
- local _result = packet
232
- if _result ~= nil then
233
- _result = _result.data
234
- end
235
- callback(_exp, _result)
236
- end
237
- end))
238
- end
239
- return self
399
+ function MessageEmitter:once(message, callback, callbacksMap)
400
+ local destructor
401
+ destructor = self:on(message, function(player, data)
402
+ destructor()
403
+ callback(player, data)
404
+ end, callbacksMap)
405
+ return destructor
240
406
  end
241
- function MessageEmitter:on(message, callback)
242
- local callbacksMap = if RunService:IsServer() then self.serverCallbacks else self.clientCallbacks
407
+ function MessageEmitter:on(message, callback, callbacksMap)
408
+ local _callbacksMap = callbacksMap
243
409
  local _message = message
244
- if not (callbacksMap[_message] ~= nil) then
410
+ if not (_callbacksMap[_message] ~= nil) then
411
+ local _callbacksMap_1 = callbacksMap
245
412
  local _message_1 = message
246
- callbacksMap[_message_1] = {}
413
+ _callbacksMap_1[_message_1] = {}
247
414
  end
415
+ local _callbacksMap_1 = callbacksMap
248
416
  local _message_1 = message
249
- local callbacks = callbacksMap[_message_1]
417
+ local callbacks = _callbacksMap_1[_message_1]
250
418
  local _callback = callback
251
419
  callbacks[_callback] = true
420
+ local _callbacksMap_2 = callbacksMap
252
421
  local _message_2 = message
253
- callbacksMap[_message_2] = callbacks
422
+ _callbacksMap_2[_message_2] = callbacks
254
423
  return function()
255
424
  local _callback_1 = callback
256
425
  -- ▼ Set.delete ▼
@@ -262,7 +431,7 @@ do
262
431
  end
263
432
  function MessageEmitter:getPacket(message, data)
264
433
  local serializer = self:getSerializer(message)
265
- if serializer ~= nil then
434
+ if serializer ~= nil and data ~= nil then
266
435
  return serializer.serialize({
267
436
  message = message,
268
437
  data = data,
@@ -1,13 +1,18 @@
1
- import type { BaseMessage } from "./structs";
1
+ import type { BaseMessage, SerializedPacket } from "./structs";
2
2
  type DropRequestSymbol = symbol & {
3
3
  _drop_req?: undefined;
4
4
  };
5
5
  export declare const DropRequest: DropRequestSymbol;
6
6
  type UpdateDataFn<T> = (newData: T) => void;
7
- export type ClientMiddleware<Data = unknown> = (message: BaseMessage) => (player: Player | Player[], data: Readonly<Data>, updateData: UpdateDataFn<Data>) => DropRequestSymbol | void;
8
- export type ServerMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>) => DropRequestSymbol | void;
9
- export type SharedMiddleware = (message: BaseMessage) => () => DropRequestSymbol | void;
10
- export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data>;
7
+ type GetRawDataFn = () => SerializedPacket;
8
+ export type ClientMiddleware<Data = unknown> = {
9
+ _client?: void;
10
+ } & ((message: BaseMessage) => (player: Player | Player[], data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: GetRawDataFn) => DropRequestSymbol | void);
11
+ export type ServerMiddleware<Data = unknown> = SharedMiddleware<Data> & {
12
+ _server?: void;
13
+ };
14
+ export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: GetRawDataFn) => DropRequestSymbol | void;
15
+ export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
11
16
  export declare class MiddlewareProvider<MessageData> {
12
17
  private readonly clientGlobalMiddlewares;
13
18
  private readonly serverGlobalMiddlewares;
@@ -21,9 +26,9 @@ export declare class MiddlewareProvider<MessageData> {
21
26
  getClientGlobal<Data>(): ClientMiddleware<Data>[];
22
27
  /** @hidden */
23
28
  getServerGlobal<Data>(): ServerMiddleware<Data>[];
24
- useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[], order?: number): this;
25
- useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[], order?: number): this;
26
- useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
29
+ useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[] | ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
30
+ useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[] | ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
31
+ useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware<MessageData[Kind]> | readonly SharedMiddleware<MessageData[Kind]>[] | SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
27
32
  useClientGlobal(middlewares: ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
28
33
  useServerGlobal(middlewares: ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
29
34
  useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
@@ -71,8 +71,25 @@ do
71
71
  return self
72
72
  end
73
73
  function MiddlewareProvider:useShared(message, middlewares, order)
74
- self:useClient(message, middlewares, order)
75
- self:useServer(message, middlewares, order)
74
+ local server = middlewares
75
+ local _middlewares = middlewares
76
+ local _exp = (if type(_middlewares) == "function" then { middlewares } else middlewares)
77
+ -- ▼ ReadonlyArray.map ▼
78
+ local _newValue = table.create(#_exp)
79
+ local _callback = function(middleware)
80
+ return function(message)
81
+ return function(_, data, updateData, getRawData)
82
+ return middleware(message)(data, updateData, getRawData)
83
+ end
84
+ end
85
+ end
86
+ for _k, _v in _exp do
87
+ _newValue[_k] = _callback(_v, _k - 1, _exp)
88
+ end
89
+ -- ▲ ReadonlyArray.map ▲
90
+ local client = _newValue
91
+ self:useServer(message, server, order)
92
+ self:useClient(message, client, order)
76
93
  return self
77
94
  end
78
95
  function MiddlewareProvider:useClientGlobal(middlewares, order)
@@ -110,7 +127,23 @@ do
110
127
  return self
111
128
  end
112
129
  function MiddlewareProvider:useSharedGlobal(middlewares, order)
113
- self:useClientGlobal(middlewares, order)
130
+ local _middlewares = middlewares
131
+ local _exp = (if type(_middlewares) == "function" then { middlewares } else middlewares)
132
+ -- ▼ ReadonlyArray.map ▼
133
+ local _newValue = table.create(#_exp)
134
+ local _callback = function(middleware)
135
+ return function(message)
136
+ return function(_, data, updateData, getRawData)
137
+ return middleware(message)(data, updateData, getRawData)
138
+ end
139
+ end
140
+ end
141
+ for _k, _v in _exp do
142
+ _newValue[_k] = _callback(_v, _k - 1, _exp)
143
+ end
144
+ -- ▲ ReadonlyArray.map ▲
145
+ local client = _newValue
146
+ self:useClientGlobal(client, order)
114
147
  self:useServerGlobal(middlewares, order)
115
148
  return self
116
149
  end
package/out/structs.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ import { Modding } from "@flamework/core";
1
2
  import type { Networking } from "@flamework/networking";
2
- import type { DataType } from "@rbxts/flamework-binary-serializer";
3
+ import type { DataType, SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
4
  export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
4
5
  export type ClientMessageCallback<T = unknown> = (data: T) => void;
6
+ export type ClientMessageFunctionCallback<T = unknown, R = unknown> = (data: T) => R;
5
7
  export type ServerMessageCallback<T = unknown> = (player: Player, data: T) => void;
8
+ export type ServerMessageFunctionCallback<T = unknown, R = unknown> = (player: Player, data: T) => R;
6
9
  export type BaseMessage = number;
7
10
  export interface SerializedPacket {
8
11
  readonly buffer: buffer;
@@ -10,7 +13,7 @@ export interface SerializedPacket {
10
13
  }
11
14
  export interface TetherPacket<Data> {
12
15
  readonly message: DataType.u8;
13
- readonly data?: Data;
16
+ readonly data: Data;
14
17
  }
15
18
  export type MessageEvent = (packet: SerializedPacket) => void;
16
19
  export type UnreliableMessageEvent = Networking.Unreliable<MessageEvent>;
@@ -22,3 +25,11 @@ export interface ClientEvents {
22
25
  sendClientMessage: MessageEvent;
23
26
  sendUnreliableClientMessage: UnreliableMessageEvent;
24
27
  }
28
+ export interface MessageMetadata<MessageData, Kind extends keyof MessageData> {
29
+ readonly guard: Modding.Generic<MessageData[Kind], "guard">;
30
+ readonly serializerMetadata: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<TetherPacket<MessageData[Kind]>>>;
31
+ }
32
+ export type Guard<T = unknown> = (value: unknown) => value is T;
33
+ export type MessageEmitterMetadata<MessageData> = {
34
+ [Kind in keyof MessageData]: MessageMetadata<MessageData, Kind>;
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "main": "out/init.lua",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "author": "runicly",
22
22
  "license": "ISC",
23
- "description": "A message-based networking solution for Roblox with automatic binary serialization",
23
+ "description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
24
24
  "types": "out/index.d.ts",
25
25
  "files": [
26
26
  "out",
@@ -34,6 +34,7 @@
34
34
  "@rbxts/types": "^1.0.835",
35
35
  "rbxts-transformer-flamework": "^1.2.4",
36
36
  "roblox-ts": "^3.0.0",
37
+ "ts-toolbelt": "^9.6.0",
37
38
  "typescript": "^5.5.3"
38
39
  },
39
40
  "dependencies": {
@@ -41,6 +42,8 @@
41
42
  "@flamework/networking": "^1.2.4",
42
43
  "@rbxts/destroyable": "^1.0.1",
43
44
  "@rbxts/flamework-binary-serializer": "^0.6.0",
45
+ "@rbxts/flamework-meta-utils": "^1.0.4",
46
+ "@rbxts/repr": "^1.0.1",
44
47
  "@rbxts/services": "^1.5.5"
45
48
  }
46
49
  }