@rbxts/tether 1.2.1 → 1.2.2

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,197 +1,197 @@
1
- # Tether
2
- A message-based networking solution for Roblox with automatic binary serialization and type validation.
3
-
4
- > [!CAUTION]
5
- > Depends on `rbxts-transformer-flamework`!
6
-
7
- ## Usage
8
-
9
- ### In `shared/messaging.ts`
10
- ```ts
11
- import type { DataType } from "@rbxts/flamework-binary-serializer";
12
- import { MessageEmitter } from "@rbxts/tether";
13
-
14
- export const messaging = MessageEmitter.create<MessageData>();
15
-
16
- export const enum Message {
17
- Test,
18
- Packed
19
- }
20
-
21
- export interface MessageData {
22
- [Message.Test]: {
23
- readonly foo: string;
24
- readonly n: DataType.u8;
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
- }>;
36
- }
37
- ```
38
-
39
- > [!CAUTION]
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.
41
-
42
- ### Server
43
- ```ts
44
- import { Message, messaging } from "shared/messaging";
45
-
46
- messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
47
- ```
48
-
49
- ### Client
50
- ```ts
51
- import { Message, messaging } from "shared/messaging";
52
-
53
- messaging.server.emit(Message.Test, {
54
- foo: "bar",
55
- n: 69
56
- });
57
- ```
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
-
106
- ## Middleware
107
- Drop, delay, or modify requests
108
-
109
- ### Creating middleware
110
-
111
- **Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
112
- #### Client
113
- ```ts
114
- import type { ClientMiddleware } from "@rbxts/tether";
115
-
116
- export function logClient(): ClientMiddleware {
117
- return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
118
- }
119
- ```
120
-
121
- #### Server
122
- ```ts
123
- import type { ServerMiddleware } from "@rbxts/tether";
124
-
125
- export function logServer(): ServerMiddleware {
126
- return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
127
- }
128
- ```
129
-
130
- #### Shared
131
- ```ts
132
- import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
133
-
134
- export function rateLimit(interval: number): SharedMiddleware {
135
- let lastRequest = 0;
136
-
137
- return message => // message attempting to be sent
138
- () => { // no data/player - it's a shared middleware
139
- if (os.clock() - lastRequest < interval)
140
- return DropRequest;
141
-
142
- lastRequest = os.clock();
143
- };
144
- }
145
- ```
146
-
147
- #### Transforming data
148
- ```ts
149
- import type { ServerMiddleware } from "@rbxts/tether";
150
-
151
- export function incrementNumberData(): ServerMiddleware<number> {
152
- // sets the data to be used by the any subsequent middlewares as well as sent through the remote
153
- return () => ({ data, updateData }) => updateData(data + 1);
154
- }
155
- ```
156
-
157
- ### Using middleware
158
- ```ts
159
- import type { DataType } from "@rbxts/flamework-binary-serializer";
160
- import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
161
-
162
- export const messaging = MessageEmitter.create<MessageData>();
163
- messaging.middleware
164
- // only allows requests to the server every 5 seconds,
165
- // drops any requests that occur within 5 seconds of each other
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)));
169
- // logs every message fired
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
175
-
176
- export const enum Message {
177
- Test,
178
- Packed
179
- }
180
-
181
- export interface MessageData {
182
- [Message.Test]: {
183
- readonly foo: string;
184
- readonly n: DataType.u8;
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
- }>;
196
- }
1
+ # Tether
2
+ A message-based networking solution for Roblox with automatic binary serialization and type validation.
3
+
4
+ > [!CAUTION]
5
+ > Depends on `rbxts-transformer-flamework`!
6
+
7
+ ## Usage
8
+
9
+ ### In `shared/messaging.ts`
10
+ ```ts
11
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
12
+ import { MessageEmitter } from "@rbxts/tether";
13
+
14
+ export const messaging = MessageEmitter.create<MessageData>();
15
+
16
+ export const enum Message {
17
+ Test,
18
+ Packed
19
+ }
20
+
21
+ export interface MessageData {
22
+ [Message.Test]: {
23
+ readonly foo: string;
24
+ readonly n: DataType.u8;
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
+ }>;
36
+ }
37
+ ```
38
+
39
+ > [!CAUTION]
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.
41
+
42
+ ### Server
43
+ ```ts
44
+ import { Message, messaging } from "shared/messaging";
45
+
46
+ messaging.server.on(Message.Test, (player, data) => print(player, "sent data:", data));
47
+ ```
48
+
49
+ ### Client
50
+ ```ts
51
+ import { Message, messaging } from "shared/messaging";
52
+
53
+ messaging.server.emit(Message.Test, {
54
+ foo: "bar",
55
+ n: 69
56
+ });
57
+ ```
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
+
106
+ ## Middleware
107
+ Drop, delay, or modify requests
108
+
109
+ ### Creating middleware
110
+
111
+ **Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
112
+ #### Client
113
+ ```ts
114
+ import type { ClientMiddleware } from "@rbxts/tether";
115
+
116
+ export function logClient(): ClientMiddleware {
117
+ return message => (player, ctx) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, ctx.data);
118
+ }
119
+ ```
120
+
121
+ #### Server
122
+ ```ts
123
+ import type { ServerMiddleware } from "@rbxts/tether";
124
+
125
+ export function logServer(): ServerMiddleware {
126
+ return message => ctx => print(`[LOG]: Sent message '${message}' to server with data:`, ctx.data);
127
+ }
128
+ ```
129
+
130
+ #### Shared
131
+ ```ts
132
+ import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
133
+
134
+ export function rateLimit(interval: number): SharedMiddleware {
135
+ let lastRequest = 0;
136
+
137
+ return message => // message attempting to be sent
138
+ () => { // no data/player - it's a shared middleware
139
+ if (os.clock() - lastRequest < interval)
140
+ return DropRequest;
141
+
142
+ lastRequest = os.clock();
143
+ };
144
+ }
145
+ ```
146
+
147
+ #### Transforming data
148
+ ```ts
149
+ import type { ServerMiddleware } from "@rbxts/tether";
150
+
151
+ export function incrementNumberData(): ServerMiddleware<number> {
152
+ // sets the data to be used by the any subsequent middlewares as well as sent through the remote
153
+ return () => ({ data, updateData }) => updateData(data + 1);
154
+ }
155
+ ```
156
+
157
+ ### Using middleware
158
+ ```ts
159
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
160
+ import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
161
+
162
+ export const messaging = MessageEmitter.create<MessageData>();
163
+ messaging.middleware
164
+ // only allows requests to the server every 5 seconds,
165
+ // drops any requests that occur within 5 seconds of each other
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)));
169
+ // logs every message fired
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
175
+
176
+ export const enum Message {
177
+ Test,
178
+ Packed
179
+ }
180
+
181
+ export interface MessageData {
182
+ [Message.Test]: {
183
+ readonly foo: string;
184
+ readonly n: DataType.u8;
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
+ }>;
196
+ }
197
197
  ```
@@ -1,8 +1,8 @@
1
1
  import { Modding } from "@flamework/core";
2
2
  import type { SerializerMetadata } from "@rbxts/flamework-binary-serializer";
3
+ import type { Any } from "ts-toolbelt";
3
4
  import { type SharedMiddleware } from "./middleware";
4
5
  import type { TetherPacket } from "./structs";
5
- import { Any } from "ts-toolbelt";
6
6
  export declare namespace BuiltinMiddlewares {
7
7
  /**
8
8
  * Creates a shared middleware that will simulate a ping of the given amount when a message is sent
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@rbxts/tether",
3
- "version": "1.2.1",
4
- "main": "out/init.lua",
5
- "scripts": {
6
- "build": "rbxtsc",
7
- "watch": "rbxtsc -w",
8
- "prepublishOnly": "npm run build"
9
- },
10
- "keywords": [
11
- "roblox",
12
- "tether",
13
- "networking",
14
- "message",
15
- "serialization",
16
- "middleware"
17
- ],
18
- "repository": {
19
- "url": "git+https://github.com/R-unic/tether.git"
20
- },
21
- "author": "runicly",
22
- "license": "ISC",
23
- "description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
24
- "types": "out/index.d.ts",
25
- "files": [
26
- "out",
27
- "!**/*.tsbuildinfo"
28
- ],
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "devDependencies": {
33
- "@rbxts/compiler-types": "^3.0.0-types.0",
34
- "@rbxts/types": "^1.0.835",
35
- "rbxts-transformer-flamework": "^1.2.4",
36
- "roblox-ts": "^3.0.0",
37
- "ts-toolbelt": "^9.6.0",
38
- "typescript": "^5.5.3"
39
- },
40
- "dependencies": {
41
- "@flamework/core": "^1.2.4",
42
- "@flamework/networking": "^1.2.4",
43
- "@rbxts/destroyable": "^1.0.1",
44
- "@rbxts/flamework-binary-serializer": "^0.6.0",
45
- "@rbxts/flamework-meta-utils": "^1.0.4",
46
- "@rbxts/repr": "^1.0.1",
47
- "@rbxts/services": "^1.5.5"
48
- }
49
- }
1
+ {
2
+ "name": "@rbxts/tether",
3
+ "version": "1.2.2",
4
+ "main": "out/init.lua",
5
+ "scripts": {
6
+ "build": "rbxtsc",
7
+ "watch": "rbxtsc -w",
8
+ "prepublishOnly": "npm run build"
9
+ },
10
+ "keywords": [
11
+ "roblox",
12
+ "tether",
13
+ "networking",
14
+ "message",
15
+ "serialization",
16
+ "middleware"
17
+ ],
18
+ "repository": {
19
+ "url": "git+https://github.com/R-unic/tether.git"
20
+ },
21
+ "author": "runicly",
22
+ "license": "ISC",
23
+ "description": "A message-based networking solution for Roblox with automatic binary serialization and type validation",
24
+ "types": "out/index.d.ts",
25
+ "files": [
26
+ "out",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "devDependencies": {
33
+ "@rbxts/compiler-types": "^3.0.0-types.0",
34
+ "@rbxts/types": "^1.0.835",
35
+ "rbxts-transformer-flamework": "^1.2.4",
36
+ "roblox-ts": "^3.0.0",
37
+ "typescript": "^5.5.3"
38
+ },
39
+ "dependencies": {
40
+ "@flamework/core": "^1.2.4",
41
+ "@flamework/networking": "^1.2.4",
42
+ "@rbxts/destroyable": "^1.0.1",
43
+ "@rbxts/flamework-binary-serializer": "^0.6.0",
44
+ "@rbxts/flamework-meta-utils": "^1.0.4",
45
+ "@rbxts/repr": "^1.0.1",
46
+ "@rbxts/services": "^1.5.5",
47
+ "ts-toolbelt": "^9.6.0"
48
+ }
49
+ }