@rbxts/tether 1.2.0 → 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,198 +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
- boolean1: boolean;
28
- boolean2: boolean;
29
- boolean3: boolean;
30
- boolean4: boolean;
31
- boolean5: boolean;
32
- boolean6: boolean;
33
- boolean7: boolean;
34
- 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 with the `foo` and `bar` fields). 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) => {
47
- print(player, "sent data:", data);
48
- });
49
- ```
50
-
51
- ### Client
52
- ```ts
53
- import { Message, messaging } from "shared/messaging";
54
-
55
- messaging.server.emit(Message.Test, {
56
- foo: "bar",
57
- n: 69
58
- });
59
- ```
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
-
108
- ## Middleware
109
- Drop, delay, or modify requests
110
-
111
- ### Creating middleware
112
-
113
- **Note:** These client/server middlewares can be implemented as shared middlewares. This is strictly an example.
114
- #### Client
115
- ```ts
116
- import type { ClientMiddleware } from "@rbxts/tether";
117
-
118
- export function logClient(): ClientMiddleware {
119
- return message => (player, data) => print(`[LOG]: Sent message '${message}' to player ${player} with data:`, data);
120
- }
121
- ```
122
-
123
- #### Server
124
- ```ts
125
- import type { ServerMiddleware } from "@rbxts/tether";
126
-
127
- export function logServer(): ServerMiddleware {
128
- return message => data => print(`[LOG]: Sent message '${message}' to server with data:`, data);
129
- }
130
- ```
131
-
132
- #### Shared
133
- ```ts
134
- import { type SharedMiddleware, DropRequest } from "@rbxts/tether";
135
-
136
- export function rateLimit(interval: number): SharedMiddleware {
137
- let lastRequest = 0;
138
-
139
- return message => // message attempting to be sent
140
- () => { // no data/player - it's a shared middleware
141
- if (os.clock() - lastRequest < interval)
142
- return DropRequest;
143
-
144
- lastRequest = os.clock();
145
- };
146
- }
147
- ```
148
-
149
- #### Transforming data
150
- ```ts
151
- import type { ServerMiddleware } from "@rbxts/tether";
152
-
153
- export function incrementNumberData(): ServerMiddleware<number> {
154
- // sets the data to be used by the any subsequent middlewares as well as sent through the remote
155
- return () => (data, updateData) => updateData(data + 1);
156
- }
157
- ```
158
-
159
- ### Using middleware
160
- ```ts
161
- import type { DataType } from "@rbxts/flamework-binary-serializer";
162
- import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
163
-
164
- export const messaging = MessageEmitter.create<MessageData>();
165
- messaging.middleware
166
- // only allows requests to the server every 5 seconds,
167
- // drops any requests that occur within 5 seconds of each other
168
- .useServer(Message.Test, BuiltinMiddlewares.rateLimit(5))
169
- .useShared(Message.Packed, () => (_, __, getRawData) => print("Packed object size:", buffer.len(getRawData()))); // will be just one byte!
170
- // logs every message fired
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
176
-
177
- export const enum Message {
178
- Test,
179
- Packed
180
- }
181
-
182
- export interface MessageData {
183
- [Message.Test]: {
184
- readonly foo: string;
185
- readonly n: DataType.u8;
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
- }>;
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
+ }
198
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
@@ -11,6 +11,14 @@ export declare namespace BuiltinMiddlewares {
11
11
  * @returns A shared middleware that will simulate a ping
12
12
  */
13
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;
14
22
  /**
15
23
  * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
16
24
  *
@@ -24,6 +24,30 @@ do
24
24
  end
25
25
  end
26
26
  _container.simulatePing = simulatePing
27
+ --[[
28
+ *
29
+ * Creates a shared middleware that will check if a message packet exceeds the given maximum size in bytes
30
+ *
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
34
+
35
+ ]]
36
+ local function maxPacketSize(maxBytes, throwError)
37
+ if throwError == nil then
38
+ throwError = true
39
+ end
40
+ return function(message)
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
27
51
  --[[
28
52
  *
29
53
  * Creates a shared middleware that will drop any message that occurs within the given interval of the previous message
@@ -87,7 +111,9 @@ do
87
111
  ]]
88
112
  local function debug(schema)
89
113
  return function(message)
90
- return function(data, _, getRawData)
114
+ return function(_param)
115
+ local data = _param.data
116
+ local getRawData = _param.getRawData
91
117
  local rawData = getRawData()
92
118
  local bufferSize = buffer.len(rawData.buffer)
93
119
  local blobsSize = #rawData.blobs * BLOB_SIZE
@@ -9,7 +9,7 @@ export declare class MessageEmitter<MessageData> extends Destroyable {
9
9
  private readonly serverCallbacks;
10
10
  private readonly serverFunctions;
11
11
  private readonly guards;
12
- private serializers;
12
+ private readonly serializers;
13
13
  private serverEvents;
14
14
  private clientEvents;
15
15
  /** @metadata macro */
@@ -61,11 +61,16 @@ do
61
61
  return nil
62
62
  end
63
63
  task.spawn(function()
64
+ local ctx = {
65
+ data = data,
66
+ updateData = updateData,
67
+ getRawData = getPacket,
68
+ }
64
69
  for _, globalMiddleware in self.middleware:getServerGlobal() do
65
70
  if not self:validateData(message, data) then
66
71
  return nil
67
72
  end
68
- local result = globalMiddleware(message)(data, updateData, getPacket)
73
+ local result = globalMiddleware(message)(ctx)
69
74
  if result == DropRequest then
70
75
  return nil
71
76
  end
@@ -74,7 +79,7 @@ do
74
79
  if not self:validateData(message, data) then
75
80
  return nil
76
81
  end
77
- local result = middleware(message)(data, updateData, getPacket)
82
+ local result = middleware(message)(ctx)
78
83
  if result == DropRequest then
79
84
  return nil
80
85
  end
@@ -140,11 +145,16 @@ do
140
145
  return nil
141
146
  end
142
147
  task.spawn(function()
148
+ local ctx = {
149
+ data = data,
150
+ updateData = updateData,
151
+ getRawData = getPacket,
152
+ }
143
153
  for _, globalMiddleware in self.middleware:getClientGlobal() do
144
154
  if not self:validateData(message, data) then
145
155
  return nil
146
156
  end
147
- local result = globalMiddleware(message)(player, data, updateData, getPacket)
157
+ local result = globalMiddleware(message)(player, ctx)
148
158
  if result == DropRequest then
149
159
  return nil
150
160
  end
@@ -153,7 +163,7 @@ do
153
163
  if not self:validateData(message, data) then
154
164
  return nil
155
165
  end
156
- local result = middleware(message)(player, data, updateData, getPacket)
166
+ local result = middleware(message)(player, ctx)
157
167
  if result == DropRequest then
158
168
  return nil
159
169
  end
@@ -180,12 +190,17 @@ do
180
190
  return nil
181
191
  end
182
192
  task.spawn(function()
193
+ local ctx = {
194
+ data = data,
195
+ updateData = updateData,
196
+ getRawData = getPacket,
197
+ }
183
198
  for _, globalMiddleware in self.middleware:getClientGlobal() do
184
199
  for _1, player in Players:GetPlayers() do
185
200
  if not self:validateData(message, data) then
186
201
  return nil
187
202
  end
188
- local result = globalMiddleware(message)(player, data, updateData, getPacket)
203
+ local result = globalMiddleware(message)(player, ctx)
189
204
  if result == DropRequest then
190
205
  return nil
191
206
  end
@@ -196,7 +211,7 @@ do
196
211
  if not self:validateData(message, data) then
197
212
  return nil
198
213
  end
199
- local result = middleware(message)(player, data, updateData, getPacket)
214
+ local result = middleware(message)(player, ctx)
200
215
  if result == DropRequest then
201
216
  return nil
202
217
  end
@@ -244,7 +259,7 @@ do
244
259
  self.janitor:Add(function()
245
260
  table.clear(self.clientCallbacks)
246
261
  table.clear(self.serverCallbacks)
247
- self.serializers = nil
262
+ table.clear(self.serializers)
248
263
  self.serverEvents = nil
249
264
  self.clientEvents = nil
250
265
  end)
@@ -3,16 +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
- type GetRawDataFn = () => SerializedPacket;
8
6
  export type ClientMiddleware<Data = unknown> = {
9
7
  _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> & {
8
+ } & ((message: BaseMessage) => (player: Player | Player[], ctx: MiddlewareContext<Data>) => DropRequestSymbol | void);
9
+ export type ServerMiddleware<Data = unknown> = {
12
10
  _server?: void;
13
- };
14
- export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (data: Readonly<Data>, updateData: UpdateDataFn<Data>, getRawData: GetRawDataFn) => DropRequestSymbol | void;
11
+ } & SharedMiddleware<Data>;
12
+ export type SharedMiddleware<Data = unknown> = (message: BaseMessage) => (ctx: MiddlewareContext<Data>) => DropRequestSymbol | void;
15
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
+ }
16
19
  export declare class MiddlewareProvider<MessageData> {
17
20
  private readonly clientGlobalMiddlewares;
18
21
  private readonly serverGlobalMiddlewares;
@@ -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/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@rbxts/tether",
3
- "version": "1.2.0",
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
+ }