@rbxts/tether 1.1.6 → 1.2.1

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,31 +23,86 @@ export interface MessageData {
22
23
  readonly foo: string;
23
24
  readonly n: DataType.u8;
24
25
  };
26
+ [Message.Packed]: DataType.Packed<{
27
+ readonly boolean1: boolean;
28
+ readonly boolean2: boolean;
29
+ readonly boolean3: boolean;
30
+ readonly boolean4: boolean;
31
+ readonly boolean5: boolean;
32
+ readonly boolean6: boolean;
33
+ readonly boolean7: boolean;
34
+ readonly boolean8: boolean;
35
+ }>;
25
36
  }
26
37
  ```
27
38
 
28
39
  > [!CAUTION]
29
- > Every single message kind must implement an interface for it's data (in the example that would be the object with the `foo` and `bar` fields). Message serialization (as well as your message itself) will not work if you don't do this.
40
+ > Every single message kind must implement an interface for it's data (in the example that would be the object types in `MessageData`). Message serialization (as well as your message itself) will not work if you don't do this.
30
41
 
31
42
  ### Server
32
43
  ```ts
33
44
  import { Message, messaging } from "shared/messaging";
34
45
 
35
- messaging.onServerMessage(Message.Test, (player, data) => {
36
- print(player, "sent data:", data);
37
- });
46
+ messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
38
47
  ```
39
48
 
40
49
  ### Client
41
50
  ```ts
42
51
  import { Message, messaging } from "shared/messaging";
43
52
 
44
- messaging.emitServer(Message.Test, {
53
+ messaging.server.emit(Message.Test, {
45
54
  foo: "bar",
46
55
  n: 69
47
56
  });
48
57
  ```
49
58
 
59
+ ## Simulated Remote Functions
60
+ 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.
61
+
62
+ 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).
63
+
64
+ ### In `shared/messaging.ts`
65
+ ```ts
66
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
67
+ import { MessageEmitter } from "@rbxts/tether";
68
+
69
+ export const messaging = MessageEmitter.create<MessageData>();
70
+
71
+ export const enum Message {
72
+ Increment,
73
+ IncrementReturn
74
+ }
75
+
76
+ export interface MessageData {
77
+ [Message.Increment]: DataType.u8;
78
+ [Message.IncrementReturn]: DataType.u8;
79
+ }
80
+ ```
81
+
82
+ ### Server
83
+ ```ts
84
+ import { Message, messaging } from "shared/messaging";
85
+
86
+ messaging.server.setCallback(Message.Increment, Message.IncrementReturn, (_, n) => n + 1);
87
+ ```
88
+
89
+ ### Client
90
+ ```ts
91
+ import { Message, messaging } from "shared/messaging";
92
+
93
+ messaging.server
94
+ .invoke(Message.Increment, Message.IncrementReturn, 69)
95
+ .then(print); // 70 - incremented by the server
96
+
97
+ // or use await style
98
+ async function main(): Promise<void> {
99
+ const value = await messaging.server.invoke(Message.Increment, Message.IncrementReturn, 69);
100
+ print(value) // 70
101
+ }
102
+
103
+ main();
104
+ ```
105
+
50
106
  ## Middleware
51
107
  Drop, delay, or modify requests
52
108
 
@@ -58,7 +114,7 @@ Drop, delay, or modify requests
58
114
  import type { ClientMiddleware } from "@rbxts/tether";
59
115
 
60
116
  export function logClient(): ClientMiddleware {
61
- return message => (player, data) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, data);
117
+ return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
62
118
  }
63
119
  ```
64
120
 
@@ -67,7 +123,7 @@ export function logClient(): ClientMiddleware {
67
123
  import type { ServerMiddleware } from "@rbxts/tether";
68
124
 
69
125
  export function logServer(): ServerMiddleware {
70
- return message => data => print(`[LOG]: Sent message '${message}' to server with data:`, data);
126
+ return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
71
127
  }
72
128
  ```
73
129
 
@@ -94,7 +150,7 @@ import type { ServerMiddleware } from "@rbxts/tether";
94
150
 
95
151
  export function incrementNumberData(): ServerMiddleware<number> {
96
152
  // sets the data to be used by the any subsequent middlewares as well as sent through the remote
97
- return () => (data, updateData) => updateData(data + 1);
153
+ return () => ({ data, updateData }) => updateData(data + 1);
98
154
  }
99
155
  ```
100
156
 
@@ -107,19 +163,19 @@ export const messaging = MessageEmitter.create<MessageData>();
107
163
  messaging.middleware
108
164
  // only allows requests to the server every 5 seconds,
109
165
  // drops any requests that occur within 5 seconds of each other
110
- .useServer(Message.Test, [BuiltinMiddlewares.rateLimit(5)])
111
- // automatically validates that the data sent through the remote matches
112
- // the data associated with the message at runtime using type guards
113
- .useServer(Message.Test, [BuiltinMiddlewares.validate()])
166
+ .useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
167
+ // will be just one byte!
168
+ .useShared(Message.Packed, () => ctx => print("Packed object size:", buffer.len(ctx.getRawData().buffer)));
114
169
  // logs every message fired
115
- .useServerGlobal([logServer()])
116
- .useClientGlobal([logClient()])
117
- .useSharedGlobal([BuiltinMiddlewares.debug()]); // verbosely logs every packet sent
118
- .useServer(Message.Test, [incrementNumberData()]) // error! - data for Message.Test is not a number
119
- .useServerGlobal([incrementNumberData()]); // error! - global data type is always 'unknown', we cannot guarantee a number
170
+ .useServerGlobal(logServer())
171
+ .useClientGlobal(logClient())
172
+ .useSharedGlobal(BuiltinMiddlewares.debug()); // verbosely logs every packet sent
173
+ .useServer(Message.Test, incrementNumberData()) // error! - data for Message.Test is not a number
174
+ .useServerGlobal(incrementNumberData()); // error! - global data type is always 'unknown', we cannot guarantee a number
120
175
 
121
176
  export const enum Message {
122
- Test
177
+ Test,
178
+ Packed
123
179
  }
124
180
 
125
181
  export interface MessageData {
@@ -127,5 +183,15 @@ export interface MessageData {
127
183
  readonly foo: string;
128
184
  readonly n: DataType.u8;
129
185
  };
186
+ [Message.Packed]: DataType.Packed<{
187
+ readonly boolean1: boolean;
188
+ readonly boolean2: boolean;
189
+ readonly boolean3: boolean;
190
+ readonly boolean4: boolean;
191
+ readonly boolean5: boolean;
192
+ readonly boolean6: boolean;
193
+ readonly boolean7: boolean;
194
+ readonly boolean8: boolean;
195
+ }>;
130
196
  }
131
197
  ```
@@ -1,7 +1,24 @@
1
1
  import { Modding } from "@flamework/core";
2
+ import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
2
3
  import { type SharedMiddleware } from "./middleware";
3
- type Guard<T> = (value: unknown) => value is T;
4
+ import type { TetherPacket } from "./structs";
5
+ import { Any } from "ts-toolbelt";
4
6
  export declare namespace BuiltinMiddlewares {
7
+ /**
8
+ * Creates a shared middleware that will simulate a ping of the given amount when a message is sent
9
+ *
10
+ * @param pingInMs The amount of time in milliseconds that the middleware should wait
11
+ * @returns A shared middleware that will simulate a ping
12
+ */
13
+ function simulatePing(pingInMs: number): SharedMiddleware;
14
+ /**
15
+ * Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
16
+ *
17
+ * @param maxBytes The maximum size of the packet in bytes
18
+ * @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
19
+ * @returns A shared middleware that will check if a message packet exceeds the given maximum size
20
+ */
21
+ function maxPacketSize(maxBytes: number, throwError?: boolean): SharedMiddleware;
5
22
  /**
6
23
  * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
7
24
  *
@@ -10,15 +27,17 @@ export declare namespace BuiltinMiddlewares {
10
27
  */
11
28
  function rateLimit(interval: number): SharedMiddleware;
12
29
  /**
13
- * Creates a shared middleware that validates the data with the given guard (or a generated guard if none was provided)
14
- *
15
- * If the guard fails, the middleware will drop the message
30
+ * Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
31
+ * - The message kind
32
+ * - The data associated with the message
33
+ * - The raw data (buffer and blobs) associated with the message
34
+ * - The size of the packet (in bytes)
35
+ * - The size of the buffer (in bytes)
36
+ * - The size of the blobs (in bytes)
37
+ * - The schema string associated with the message (if it can be deduced)
16
38
  *
17
- * @param guard The guard to use to validate the data.
18
- * @returns A shared middleware that validates the data with the given guard.
39
+ * @returns A shared middleware that will log a message whenever a message is sent.
19
40
  * @metadata macro
20
41
  */
21
- function validate<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): SharedMiddleware;
22
- function debug(): SharedMiddleware;
42
+ function debug<T>(schema?: Modding.Many<Any.Equals<T, unknown> extends 1 ? undefined : SerializerMetadata<TetherPacket<T>>>): SharedMiddleware<T>;
23
43
  }
24
- export {};
@@ -1,68 +1,74 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
4
3
  local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
5
- local noOp = function()
6
- return function() end
7
- end
8
- local validationGuardGenerationFailed = function()
9
- return `[@rbxts/tether]: Failed to generate guard for validate<T>() builtin middleware - skipping validation`
10
- end
11
- local guardFailed = function(message)
12
- return `[@rbxts/tether]: Type validation guard failed for message '{tostring(message)}' - check your sent data`
13
- end
4
+ local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
5
+ local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
14
6
  local BLOB_SIZE = 5
15
7
  local BuiltinMiddlewares = {}
16
8
  do
17
9
  local _container = BuiltinMiddlewares
18
10
  --[[
19
11
  *
20
- * Creates a shared 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
21
13
  *
22
- * @param interval The interval in seconds that the middleware should wait before allowing a new request
23
- * @returns A middleware that will drop any message that occurs within the given interval
14
+ * @param pingInMs The amount of time in milliseconds that the middleware should wait
15
+ * @returns A shared middleware that will simulate a ping
24
16
 
25
17
  ]]
26
- local function rateLimit(interval)
27
- local lastRequest = 0
18
+ local function simulatePing(pingInMs)
28
19
  return function()
29
20
  return function()
30
- if os.clock() - lastRequest < interval then
31
- return DropRequest
32
- end
33
- lastRequest = os.clock()
21
+ task.wait(pingInMs / 1000)
22
+ return nil
34
23
  end
35
24
  end
36
25
  end
37
- _container.rateLimit = rateLimit
26
+ _container.simulatePing = simulatePing
38
27
  --[[
39
28
  *
40
- * Creates a shared middleware that validates the data with the given guard (or a generated guard if none was provided)
41
- *
42
- * If the guard fails, the middleware will drop the message
29
+ * Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
43
30
  *
44
- * @param guard The guard to use to validate the data.
45
- * @returns A shared middleware that validates the data with the given guard.
46
- * @metadata macro
31
+ * @param maxBytes The maximum size of the packet in bytes
32
+ * @param throwError Whether the middleware should throw an error if the packet exceeds the maximum size, or simply drop the request
33
+ * @returns A shared middleware that will check if a message packet exceeds the given maximum size
47
34
 
48
35
  ]]
49
- local function validate(guard)
50
- if guard == nil then
51
- warn(validationGuardGenerationFailed())
52
- return noOp
36
+ local function maxPacketSize(maxBytes, throwError)
37
+ if throwError == nil then
38
+ throwError = true
53
39
  end
54
40
  return function(message)
55
- return function(data)
56
- if guard(data) then
57
- return nil
41
+ return function(ctx)
42
+ local rawData = ctx.getRawData()
43
+ local totalSize = buffer.len(rawData.buffer) + #rawData.blobs * BLOB_SIZE
44
+ if totalSize > maxBytes then
45
+ return if throwError then error(`[@rbxts/tether]: Message '{message}' exceeded maximum packet size of {maxBytes} bytes`) else DropRequest
46
+ end
47
+ end
48
+ end
49
+ end
50
+ _container.maxPacketSize = maxPacketSize
51
+ --[[
52
+ *
53
+ * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
54
+ *
55
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request
56
+ * @returns A middleware that will drop any message that occurs within the given interval
57
+
58
+ ]]
59
+ local function rateLimit(interval)
60
+ local lastRequest = 0
61
+ return function()
62
+ return function()
63
+ if os.clock() - lastRequest < interval then
64
+ return DropRequest
58
65
  end
59
- warn(guardFailed(message))
60
- return DropRequest
66
+ lastRequest = os.clock()
61
67
  end
62
68
  end
63
69
  end
64
- _container.validate = validate
65
- local function toStringBuffer(buf)
70
+ _container.rateLimit = rateLimit
71
+ local function bufferToString(buf)
66
72
  local s = { "{ " }
67
73
  do
68
74
  local i = 0
@@ -87,24 +93,44 @@ do
87
93
  table.insert(s, " }")
88
94
  return table.concat(s, "")
89
95
  end
90
- local line = "------------------------------------"
91
- local function debug()
96
+ local horizontalLine = "------------------------------------"
97
+ --[[
98
+ *
99
+ * Creates a shared middleware that will log a message whenever a message is sent, containing the following information:
100
+ * - The message kind
101
+ * - The data associated with the message
102
+ * - The raw data (buffer and blobs) associated with the message
103
+ * - The size of the packet (in bytes)
104
+ * - The size of the buffer (in bytes)
105
+ * - The size of the blobs (in bytes)
106
+ * - The schema string associated with the message (if it can be deduced)
107
+ *
108
+ * @returns A shared middleware that will log a message whenever a message is sent.
109
+ * @metadata macro
110
+
111
+ ]]
112
+ local function debug(schema)
92
113
  return function(message)
93
- return function(data, _, getRawData)
114
+ return function(_param)
115
+ local data = _param.data
116
+ local getRawData = _param.getRawData
94
117
  local rawData = getRawData()
95
118
  local bufferSize = buffer.len(rawData.buffer)
96
119
  local blobsSize = #rawData.blobs * BLOB_SIZE
97
- print(line)
98
- print("Packet sent to", (if RunService:IsServer() then "client" else "server") .. "!")
99
- print(" - Message:", message)
100
- print(" - Data:", data)
101
- print(" - Raw data:")
102
- print(" - Buffer:", toStringBuffer(rawData.buffer))
103
- print(" - Blobs:", rawData.blobs)
104
- print(" - Packet size:", bufferSize + blobsSize, "bytes")
105
- print(" - Buffer:", bufferSize, "bytes")
106
- print(" - Blobs:", blobsSize, "bytes")
107
- print(line)
120
+ local schemaString = if schema ~= nil then " " .. table.concat(string.split(repr(schema[2], {
121
+ pretty = true,
122
+ }), "\n"), "\n ") else "unknown"
123
+ 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, {
124
+ pretty = false,
125
+ robloxClassName = true,
126
+ }), "\n", " - Packet size: ", bufferSize + blobsSize, " bytes\n", " - Buffer: ", bufferSize, " bytes\n", " - Blobs: ", blobsSize, " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
127
+ -- ReadonlyArray.join
128
+ local _result = table.create(#text)
129
+ for _k, _v in text do
130
+ _result[_k] = tostring(_v)
131
+ end
132
+ -- ▲ ReadonlyArray.join ▲
133
+ print(table.concat(_result, ""))
108
134
  end
109
135
  end
110
136
  end
@@ -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 serializers;
10
+ private readonly serverFunctions;
11
+ private readonly guards;
12
+ private readonly 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,17 +34,237 @@ 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
+ local ctx = {
65
+ data = data,
66
+ updateData = updateData,
67
+ getRawData = getPacket,
68
+ }
69
+ for _, globalMiddleware in self.middleware:getServerGlobal() do
70
+ if not self:validateData(message, data) then
71
+ return nil
72
+ end
73
+ local result = globalMiddleware(message)(ctx)
74
+ if result == DropRequest then
75
+ return nil
76
+ end
77
+ end
78
+ for _, middleware in self.middleware:getServer(message) do
79
+ if not self:validateData(message, data) then
80
+ return nil
81
+ end
82
+ local result = middleware(message)(ctx)
83
+ if result == DropRequest then
84
+ return nil
85
+ end
86
+ end
87
+ if not self:validateData(message, data) then
88
+ return nil
89
+ end
90
+ local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
91
+ send(getPacket())
92
+ end)
93
+ end,
94
+ invoke = TS.async(function(message, returnMessage, data, unreliable)
95
+ if unreliable == nil then
96
+ unreliable = false
97
+ end
98
+ local _clientFunctions = self.clientFunctions
99
+ local _returnMessage = returnMessage
100
+ if not (_clientFunctions[_returnMessage] ~= nil) then
101
+ local _clientFunctions_1 = self.clientFunctions
102
+ local _returnMessage_1 = returnMessage
103
+ _clientFunctions_1[_returnMessage_1] = {}
104
+ end
105
+ local _clientFunctions_1 = self.clientFunctions
106
+ local _returnMessage_1 = returnMessage
107
+ local functions = _clientFunctions_1[_returnMessage_1]
108
+ local returnValue
109
+ functions[function(data)
110
+ returnValue = data
111
+ return returnValue
112
+ end] = true
113
+ self.server.emit(message, data, unreliable)
114
+ while returnValue == nil do
115
+ RunService.Heartbeat:Wait()
116
+ end
117
+ return returnValue
118
+ end),
119
+ setCallback = function(message, returnMessage, callback)
120
+ return self.server.on(message, function(player, data)
121
+ local returnValue = callback(player, data)
122
+ self.client.emit(player, returnMessage, returnValue)
123
+ end)
124
+ end,
125
+ }
126
+ self.client = {
127
+ on = function(message, callback)
128
+ return self:on(message, callback, self.clientCallbacks)
129
+ end,
130
+ once = function(message, callback)
131
+ return self:once(message, callback, self.clientCallbacks)
132
+ end,
133
+ emit = function(player, message, data, unreliable)
134
+ if unreliable == nil then
135
+ unreliable = false
136
+ end
137
+ local updateData = function(newData)
138
+ data = newData
139
+ return nil
140
+ end
141
+ local getPacket = function()
142
+ return self:getPacket(message, data)
143
+ end
144
+ if not self:validateData(message, data) then
145
+ return nil
146
+ end
147
+ task.spawn(function()
148
+ local ctx = {
149
+ data = data,
150
+ updateData = updateData,
151
+ getRawData = getPacket,
152
+ }
153
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
154
+ if not self:validateData(message, data) then
155
+ return nil
156
+ end
157
+ local result = globalMiddleware(message)(player, ctx)
158
+ if result == DropRequest then
159
+ return nil
160
+ end
161
+ end
162
+ for _, middleware in self.middleware:getClient(message) do
163
+ if not self:validateData(message, data) then
164
+ return nil
165
+ end
166
+ local result = middleware(message)(player, ctx)
167
+ if result == DropRequest then
168
+ return nil
169
+ end
170
+ end
171
+ if not self:validateData(message, data) then
172
+ return nil
173
+ end
174
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
175
+ send(player, getPacket())
176
+ end)
177
+ end,
178
+ emitAll = function(message, data, unreliable)
179
+ if unreliable == nil then
180
+ unreliable = false
181
+ end
182
+ local updateData = function(newData)
183
+ data = newData
184
+ return nil
185
+ end
186
+ local getPacket = function()
187
+ return self:getPacket(message, data)
188
+ end
189
+ if not self:validateData(message, data) then
190
+ return nil
191
+ end
192
+ task.spawn(function()
193
+ local ctx = {
194
+ data = data,
195
+ updateData = updateData,
196
+ getRawData = getPacket,
197
+ }
198
+ for _, globalMiddleware in self.middleware:getClientGlobal() do
199
+ for _1, player in Players:GetPlayers() do
200
+ if not self:validateData(message, data) then
201
+ return nil
202
+ end
203
+ local result = globalMiddleware(message)(player, ctx)
204
+ if result == DropRequest then
205
+ return nil
206
+ end
207
+ end
208
+ end
209
+ for _, middleware in self.middleware:getClient(message) do
210
+ for _1, player in Players:GetPlayers() do
211
+ if not self:validateData(message, data) then
212
+ return nil
213
+ end
214
+ local result = middleware(message)(player, ctx)
215
+ if result == DropRequest then
216
+ return nil
217
+ end
218
+ end
219
+ end
220
+ if not self:validateData(message, data) then
221
+ return nil
222
+ end
223
+ local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
224
+ send:broadcast(getPacket())
225
+ end)
226
+ end,
227
+ invoke = TS.async(function(message, returnMessage, player, data, unreliable)
228
+ if unreliable == nil then
229
+ unreliable = false
230
+ end
231
+ local _serverFunctions = self.serverFunctions
232
+ local _returnMessage = returnMessage
233
+ if not (_serverFunctions[_returnMessage] ~= nil) then
234
+ local _serverFunctions_1 = self.serverFunctions
235
+ local _returnMessage_1 = returnMessage
236
+ _serverFunctions_1[_returnMessage_1] = {}
237
+ end
238
+ local _serverFunctions_1 = self.serverFunctions
239
+ local _returnMessage_1 = returnMessage
240
+ local functions = _serverFunctions_1[_returnMessage_1]
241
+ local returnValue
242
+ functions[function(data)
243
+ returnValue = data
244
+ return returnValue
245
+ end] = true
246
+ self.client.emit(player, message, data, unreliable)
247
+ while returnValue == nil do
248
+ RunService.Heartbeat:Wait()
249
+ end
250
+ return returnValue
251
+ end),
252
+ setCallback = function(message, returnMessage, callback)
253
+ return self.client.on(message, function(data)
254
+ local returnValue = callback(data)
255
+ self.server.emit(returnMessage, returnValue)
256
+ end)
257
+ end,
258
+ }
34
259
  self.janitor:Add(function()
35
260
  table.clear(self.clientCallbacks)
36
261
  table.clear(self.serverCallbacks)
37
- self.serializers = nil
262
+ table.clear(self.serializers)
38
263
  self.serverEvents = nil
39
264
  self.clientEvents = nil
40
265
  end)
41
266
  if RunService:IsServer() then
42
- self.serverEvents = GlobalEvents:createServer({}, {
267
+ self.serverEvents = remotes:createServer({}, {
43
268
  incomingIds = { "sendServerMessage", "sendUnreliableServerMessage" },
44
269
  incoming = {
45
270
  sendServerMessage = { { t.interface({
@@ -62,7 +287,7 @@ do
62
287
  namespaces = {},
63
288
  })
64
289
  else
65
- self.clientEvents = GlobalEvents:createClient({}, {
290
+ self.clientEvents = remotes:createClient({}, {
66
291
  incomingIds = { "sendClientMessage", "sendUnreliableClientMessage" },
67
292
  incoming = {
68
293
  sendClientMessage = { { t.interface({
@@ -86,180 +311,130 @@ do
86
311
  })
87
312
  end
88
313
  end
89
- function MessageEmitter:create(metaForEachMessage)
314
+ function MessageEmitter:create(meta)
90
315
  local emitter = MessageEmitter.new()
91
- if metaForEachMessage == nil then
92
- warn("[@rbxts/tether]: Failed to generate serializer metadata for MessageEmitter")
316
+ if meta == nil then
317
+ warn(metaGenerationFailed)
93
318
  return emitter:initialize()
94
319
  end
95
- for kind, meta in pairs(metaForEachMessage) do
96
- if meta == nil then
320
+ for kind, _binding in pairs(meta) do
321
+ local guard = _binding.guard
322
+ local serializerMetadata = _binding.serializerMetadata
323
+ local numberKind = tonumber(kind)
324
+ emitter.guards[numberKind] = guard
325
+ if serializerMetadata == nil then
97
326
  continue
98
327
  end
99
- emitter:addSerializer(tonumber(kind), meta)
328
+ emitter:addSerializer(numberKind, serializerMetadata)
100
329
  end
101
330
  return emitter:initialize()
102
331
  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
- local getPacket = function()
118
- return self:getPacket(message, data)
119
- end
120
- for _, globalMiddleware in self.middleware:getServerGlobal() do
121
- local result = globalMiddleware(message)(data, updateData, getPacket)
122
- if result == DropRequest then
123
- return nil
124
- end
125
- end
126
- for _, middleware in self.middleware:getServer(message) do
127
- local result = middleware(message)(data, updateData, getPacket)
128
- if result == DropRequest then
129
- return nil
130
- end
332
+ function MessageEmitter:validateData(message, data)
333
+ local _guards = self.guards
334
+ local _message = message
335
+ local guard = _guards[_message]
336
+ local guardPassed = guard(data)
337
+ if not guardPassed then
338
+ warn(guardFailed(message))
131
339
  end
132
- local send = if unreliable then self.clientEvents.sendUnreliableServerMessage else self.clientEvents.sendServerMessage
133
- send(getPacket())
340
+ return guardPassed
134
341
  end
135
- function MessageEmitter:emitClient(player, message, data, unreliable)
136
- if unreliable == nil then
137
- unreliable = false
342
+ function MessageEmitter:initialize()
343
+ if RunService:IsClient() then
344
+ self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
345
+ local sentMessage = self:readMessageFromPacket(serializedPacket)
346
+ self:executeEventCallbacks(sentMessage, serializedPacket)
347
+ self:executeFunctions(sentMessage, serializedPacket)
348
+ end))
349
+ else
350
+ self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
351
+ local sentMessage = self:readMessageFromPacket(serializedPacket)
352
+ self:executeEventCallbacks(sentMessage, serializedPacket, player)
353
+ self:executeFunctions(sentMessage, serializedPacket)
354
+ end))
138
355
  end
139
- local updateData = function(newData)
140
- data = newData
356
+ return self
357
+ end
358
+ function MessageEmitter:readMessageFromPacket(serializedPacket)
359
+ return buffer.readu8(serializedPacket.buffer, 0)
360
+ end
361
+ function MessageEmitter:executeFunctions(message, serializedPacket)
362
+ local isServer = RunService:IsServer()
363
+ local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
364
+ local _message = message
365
+ local functions = functionsMap[_message]
366
+ if functions == nil then
141
367
  return nil
142
368
  end
143
- local getPacket = function()
144
- return self:getPacket(message, data)
145
- end
146
- for _, globalMiddleware in self.middleware:getClientGlobal() do
147
- local result = globalMiddleware(message)(player, data, updateData, getPacket)
148
- if result == DropRequest then
149
- return nil
150
- end
369
+ local serializer = self:getSerializer(message)
370
+ local _packet = serializer
371
+ if _packet ~= nil then
372
+ _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
151
373
  end
152
- for _, middleware in self.middleware:getClient(message) do
153
- local result = middleware(message)(player, data, updateData, getPacket)
154
- if result == DropRequest then
155
- return nil
374
+ local packet = _packet
375
+ for callback in functions do
376
+ local _result = packet
377
+ if _result ~= nil then
378
+ _result = _result.data
156
379
  end
380
+ callback(_result)
157
381
  end
158
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
159
- send(player, getPacket())
160
382
  end
161
- function MessageEmitter:emitAllClients(message, data, unreliable)
162
- if unreliable == nil then
163
- unreliable = false
164
- end
165
- local updateData = function(newData)
166
- data = newData
383
+ function MessageEmitter:executeEventCallbacks(message, serializedPacket, player)
384
+ local isServer = RunService:IsServer()
385
+ local callbacksMap = if isServer then self.serverCallbacks else self.clientCallbacks
386
+ local _message = message
387
+ local callbacks = callbacksMap[_message]
388
+ if callbacks == nil then
167
389
  return nil
168
390
  end
169
- local getPacket = function()
170
- return self:getPacket(message, data)
391
+ local serializer = self:getSerializer(message)
392
+ local _packet = serializer
393
+ if _packet ~= nil then
394
+ _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
171
395
  end
172
- for _, globalMiddleware in self.middleware:getClientGlobal() do
173
- for _1, player in Players:GetPlayers() do
174
- local result = globalMiddleware(message)(player, data, updateData, getPacket)
175
- if result == DropRequest then
176
- return nil
396
+ local packet = _packet
397
+ for callback in callbacks do
398
+ if isServer then
399
+ local _exp = player
400
+ local _result = packet
401
+ if _result ~= nil then
402
+ _result = _result.data
177
403
  end
178
- end
179
- end
180
- for _, middleware in self.middleware:getClient(message) do
181
- for _1, player in Players:GetPlayers() do
182
- local result = middleware(message)(player, data, updateData, getPacket)
183
- if result == DropRequest then
184
- return nil
404
+ callback(_exp, _result)
405
+ else
406
+ local _result = packet
407
+ if _result ~= nil then
408
+ _result = _result.data
185
409
  end
410
+ callback(_result)
186
411
  end
187
412
  end
188
- local send = if unreliable then self.serverEvents.sendUnreliableClientMessage else self.serverEvents.sendClientMessage
189
- send:broadcast(self:getPacket(message, data))
190
413
  end
191
- function MessageEmitter:initialize()
192
- if RunService:IsClient() then
193
- self.janitor:Add(self.clientEvents.sendClientMessage:connect(function(serializedPacket)
194
- local sentMessage = buffer.readu8(serializedPacket.buffer, 0)
195
- local messageCallbacks = self.clientCallbacks[sentMessage] or {}
196
- -- ▼ ReadonlySet.size ▼
197
- local _size = 0
198
- for _ in messageCallbacks do
199
- _size += 1
200
- end
201
- -- ▲ ReadonlySet.size ▲
202
- if _size == 0 then
203
- return nil
204
- end
205
- local serializer = self:getSerializer(sentMessage)
206
- local _packet = serializer
207
- if _packet ~= nil then
208
- _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
209
- end
210
- local packet = _packet
211
- for callback in messageCallbacks do
212
- local _result = packet
213
- if _result ~= nil then
214
- _result = _result.data
215
- end
216
- callback(_result)
217
- end
218
- end))
219
- else
220
- self.janitor:Add(self.serverEvents.sendServerMessage:connect(function(player, serializedPacket)
221
- local sentMessage = buffer.readu8(serializedPacket.buffer, 0)
222
- local messageCallbacks = self.serverCallbacks[sentMessage] or {}
223
- -- ▼ ReadonlySet.size ▼
224
- local _size = 0
225
- for _ in messageCallbacks do
226
- _size += 1
227
- end
228
- -- ▲ ReadonlySet.size ▲
229
- if _size == 0 then
230
- return nil
231
- end
232
- local serializer = self:getSerializer(sentMessage)
233
- local _packet = serializer
234
- if _packet ~= nil then
235
- _packet = _packet.deserialize(serializedPacket.buffer, serializedPacket.blobs)
236
- end
237
- local packet = _packet
238
- for callback in messageCallbacks do
239
- local _exp = player
240
- local _result = packet
241
- if _result ~= nil then
242
- _result = _result.data
243
- end
244
- callback(_exp, _result)
245
- end
246
- end))
247
- end
248
- return self
414
+ function MessageEmitter:once(message, callback, callbacksMap)
415
+ local destructor
416
+ destructor = self:on(message, function(player, data)
417
+ destructor()
418
+ callback(player, data)
419
+ end, callbacksMap)
420
+ return destructor
249
421
  end
250
- function MessageEmitter:on(message, callback)
251
- local callbacksMap = if RunService:IsServer() then self.serverCallbacks else self.clientCallbacks
422
+ function MessageEmitter:on(message, callback, callbacksMap)
423
+ local _callbacksMap = callbacksMap
252
424
  local _message = message
253
- if not (callbacksMap[_message] ~= nil) then
425
+ if not (_callbacksMap[_message] ~= nil) then
426
+ local _callbacksMap_1 = callbacksMap
254
427
  local _message_1 = message
255
- callbacksMap[_message_1] = {}
428
+ _callbacksMap_1[_message_1] = {}
256
429
  end
430
+ local _callbacksMap_1 = callbacksMap
257
431
  local _message_1 = message
258
- local callbacks = callbacksMap[_message_1]
432
+ local callbacks = _callbacksMap_1[_message_1]
259
433
  local _callback = callback
260
434
  callbacks[_callback] = true
435
+ local _callbacksMap_2 = callbacksMap
261
436
  local _message_2 = message
262
- callbacksMap[_message_2] = callbacks
437
+ _callbacksMap_2[_message_2] = callbacks
263
438
  return function()
264
439
  local _callback_1 = callback
265
440
  -- ▼ Set.delete ▼
@@ -3,11 +3,19 @@ type DropRequestSymbol = symbol & {
3
3
  _drop_req?: undefined;
4
4
  };
5
5
  export declare const DropRequest: DropRequestSymbol;
6
- type UpdateDataFn<T> = (newData: T) => void;
7
- export type ClientMiddleware<Data = unknown> = (message: BaseMessage) => (player: Player | Player[], data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: () => SerializedPacket) => DropRequestSymbol | void;
8
- export type ServerMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: () => SerializedPacket) => DropRequestSymbol | void;
9
- export type SharedMiddleware = (message: BaseMessage) => (data: unknown, updateData: UpdateDataFn<unknown>, getRawData: () => SerializedPacket) => DropRequestSymbol | void;
10
- export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data>;
6
+ export type ClientMiddleware<Data = unknown> = {
7
+ _client?: void;
8
+ } & ((message: BaseMessage) => (player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
9
+ export type ServerMiddleware<Data = unknown> = {
10
+ _server?: void;
11
+ } & SharedMiddleware<Data>;
12
+ export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
13
+ export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data> & SharedMiddleware<Data>;
14
+ export interface MiddlewareContext<Data = unknown> {
15
+ readonly data: Readonly<Data>;
16
+ updateData: (newData: Data) => void;
17
+ getRawData: () => SerializedPacket;
18
+ }
11
19
  export declare class MiddlewareProvider<MessageData> {
12
20
  private readonly clientGlobalMiddlewares;
13
21
  private readonly serverGlobalMiddlewares;
@@ -21,9 +29,9 @@ export declare class MiddlewareProvider<MessageData> {
21
29
  getClientGlobal<Data>(): ClientMiddleware<Data>[];
22
30
  /** @hidden */
23
31
  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;
32
+ useClient<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ClientMiddleware<MessageData[Kind]> | readonly ClientMiddleware<MessageData[Kind]>[] | ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
33
+ useServer<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: ServerMiddleware<MessageData[Kind]> | readonly ServerMiddleware<MessageData[Kind]>[] | ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
34
+ useShared<Kind extends keyof MessageData>(message: Kind & BaseMessage, middlewares: SharedMiddleware<MessageData[Kind]> | readonly SharedMiddleware<MessageData[Kind]>[] | SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
27
35
  useClientGlobal(middlewares: ClientMiddleware | readonly ClientMiddleware[], order?: number): this;
28
36
  useServerGlobal(middlewares: ServerMiddleware | readonly ServerMiddleware[], order?: number): this;
29
37
  useSharedGlobal(middlewares: SharedMiddleware | readonly SharedMiddleware[], order?: number): this;
@@ -78,8 +78,8 @@ do
78
78
  local _newValue = table.create(#_exp)
79
79
  local _callback = function(middleware)
80
80
  return function(message)
81
- return function(_, data, updateData, getRawData)
82
- return middleware(message)(data, updateData, getRawData)
81
+ return function(_, ctx)
82
+ return middleware(message)(ctx)
83
83
  end
84
84
  end
85
85
  end
@@ -133,8 +133,8 @@ do
133
133
  local _newValue = table.create(#_exp)
134
134
  local _callback = function(middleware)
135
135
  return function(message)
136
- return function(_, data, updateData, getRawData)
137
- return middleware(message)(data, updateData, getRawData)
136
+ return function(_, ctx)
137
+ return middleware(message)(ctx)
138
138
  end
139
139
  end
140
140
  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;
@@ -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.6",
3
+ "version": "1.2.1",
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",
@@ -43,6 +43,7 @@
43
43
  "@rbxts/destroyable": "^1.0.1",
44
44
  "@rbxts/flamework-binary-serializer": "^0.6.0",
45
45
  "@rbxts/flamework-meta-utils": "^1.0.4",
46
+ "@rbxts/repr": "^1.0.1",
46
47
  "@rbxts/services": "^1.5.5"
47
48
  }
48
49
  }