@rbxts/tether 1.4.1 → 1.4.3

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.
@@ -1,7 +1,7 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
4
- local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
4
+ local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out).default
5
5
  local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
6
6
  local bufferToString = TS.import(script, script.Parent, "utility").bufferToString
7
7
  local BLOB_SIZE = 5
@@ -89,6 +89,7 @@ do
89
89
  local data = _param.data
90
90
  local getRawData = _param.getRawData
91
91
  local _binding = getRawData()
92
+ local messageBuf = _binding.messageBuf
92
93
  local buf = _binding.buf
93
94
  local blobs = _binding.blobs
94
95
  local bufferSize = if buf == nil then 0 else buffer.len(buf)
@@ -98,7 +99,7 @@ do
98
99
  }), "\n"), "\n ") else "unknown"
99
100
  local text = { "\n", horizontalLine, "\n", "Packet sent to ", (if RunService:IsServer() then "client" else "server"), "!\n", " - Message: ", tostring(message), "\n", " - Data: ", repr(data, {
100
101
  pretty = true,
101
- }), "\n", " - Raw data:\n", " - Buffer: ", bufferToString(buf), "\n", " - Blobs: ", repr(blobs, {
102
+ }), "\n", " - Raw data:\n", " - Message Buffer: ", bufferToString(messageBuf), "\n", " - Buffer: ", bufferToString(buf), "\n", " - Blobs: ", repr(blobs, {
102
103
  pretty = false,
103
104
  robloxClassName = true,
104
105
  }), "\n", " - Packet size: ", tostring(bufferSize + blobsSize), " bytes\n", " - Buffer: ", tostring(bufferSize), " bytes\n", " - Blobs: ", tostring(blobsSize), " bytes\n", " - Schema: ", schemaString, "\n", horizontalLine, "\n" }
@@ -4,6 +4,9 @@ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
4
4
  local Players = _services.Players
5
5
  local RunService = _services.RunService
6
6
  local ContextualEmitter = TS.import(script, script.Parent, "contextual-emitter").ContextualEmitter
7
+ if setLuneContext == nil then
8
+ setLuneContext = function() end
9
+ end
7
10
  local ClientEmitter
8
11
  do
9
12
  local super = ContextualEmitter
@@ -36,7 +39,7 @@ do
36
39
  if dropRequest then
37
40
  return nil
38
41
  end
39
- self.master:queueMessage(self.context, message, { player, message, newData, unreliable })
42
+ self.master.relayer:queueMessage(self.context, message, { player, message, newData, unreliable })
40
43
  end)
41
44
  end
42
45
  function ClientEmitter:emitExcept(player, message, data, unreliable)
@@ -83,7 +86,7 @@ do
83
86
  if dropRequest then
84
87
  return nil
85
88
  end
86
- self.master:queueMessage(true, message, { message, newData, unreliable })
89
+ self.master.relayer:queueMessage(true, message, { message, newData, unreliable })
87
90
  end)
88
91
  end
89
92
  function ClientEmitter:setCallback(message, returnMessage, callback)
@@ -1,52 +1,41 @@
1
1
  import { Modding } from "@flamework/core";
2
+ import { Trash } from "@rbxts/trash";
2
3
  import Destroyable from "@rbxts/destroyable";
3
4
  import { MiddlewareProvider } from "../middleware";
4
- import type { ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata } from "../structs";
5
+ import type { SerializedPacket, ClientMessageCallback, ServerMessageCallback, BaseMessage, MessageEmitterMetadata } from "../structs";
5
6
  import { ServerEmitter } from "./server-emitter";
6
7
  import { ClientEmitter } from "./client-emitter";
7
- interface MessageEmitterOptions<MessageData> {
8
+ import { Relayer } from "../relayer";
9
+ import { Serdes } from "../serdes";
10
+ export interface MessageEmitterOptions<MessageData> {
8
11
  readonly batchRemotes: boolean;
9
12
  readonly batchRate: number;
10
13
  readonly doNotBatch: Set<keyof MessageData>;
11
14
  }
12
15
  export declare class MessageEmitter<MessageData> extends Destroyable {
13
- private readonly options;
16
+ readonly options: MessageEmitterOptions<MessageData>;
14
17
  readonly server: ServerEmitter<MessageData>;
15
18
  readonly client: ClientEmitter<MessageData>;
16
19
  readonly middleware: MiddlewareProvider<MessageData>;
20
+ /** @hidden */ readonly trash: Trash;
21
+ /** @hidden */ readonly relayer: Relayer<MessageData>;
22
+ /** @hidden */ readonly serdes: Serdes<MessageData>;
17
23
  /** @hidden */ clientCallbacks: Map<keyof MessageData, Set<ClientMessageCallback>>;
18
24
  /** @hidden */ clientFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
19
25
  /** @hidden */ serverCallbacks: Map<keyof MessageData, Set<ServerMessageCallback>>;
20
26
  /** @hidden */ serverFunctions: Map<keyof MessageData, Set<(data: unknown) => void>>;
21
27
  private readonly guards;
22
- private serializers;
23
- private serverQueue;
24
- private clientBroadcastQueue;
25
- private clientQueue;
26
28
  /** @metadata macro */
27
29
  static create<MessageData>(options?: Partial<MessageEmitterOptions<MessageData>>, meta?: Modding.Many<MessageEmitterMetadata<MessageData>>): MessageEmitter<MessageData>;
28
30
  private constructor();
29
31
  /** @hidden */
30
- queueMessage<K extends keyof MessageData>(context: "client" | "server" | true, message: K & BaseMessage, data: (MessageEmitter<MessageData>["clientQueue"] | MessageEmitter<MessageData>["serverQueue"])[number]): void;
31
- /** @hidden */
32
32
  runClientMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind], player?: Player | Player[]): [boolean, MessageData[Kind]];
33
33
  /** @hidden */
34
34
  runServerMiddlewares<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind]): [boolean, MessageData[Kind]];
35
- /** Set up emitter connections */
36
- private initialize;
37
- /** Send all queued data across the network simultaneously */
38
- private relay;
35
+ /** @hidden */
36
+ onRemoteFire(isServer: boolean, serializedPackets: SerializedPacket[], player?: Player): void;
39
37
  private validateData;
40
- private onRemoteFire;
41
38
  private executeFunctions;
42
39
  private executeEventCallbacks;
43
- private shouldBatch;
44
40
  private deserializeAndValidate;
45
- private getPacket;
46
- /** @metadata macro */
47
- private addSerializer;
48
- /** @metadata macro */
49
- private createMessageSerializer;
50
- private getSerializer;
51
41
  }
52
- export {};
@@ -1,21 +1,17 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
4
- local Players = _services.Players
5
- local ReplicatedStorage = _services.ReplicatedStorage
6
- local RunService = _services.RunService
3
+ local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
7
4
  local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default
8
5
  local Object = TS.import(script, TS.getModule(script, "@rbxts", "object-utils"))
9
- local createSerializer = TS.import(script, TS.getModule(script, "@rbxts", "serio").out).default
10
- local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out)
6
+ local repr = TS.import(script, TS.getModule(script, "@rbxts", "repr").out).default
11
7
  local _middleware = TS.import(script, script.Parent.Parent, "middleware")
12
8
  local DropRequest = _middleware.DropRequest
13
9
  local MiddlewareProvider = _middleware.MiddlewareProvider
14
10
  local ServerEmitter = TS.import(script, script.Parent, "server-emitter").ServerEmitter
15
11
  local ClientEmitter = TS.import(script, script.Parent, "client-emitter").ClientEmitter
16
- local _utility = TS.import(script, script.Parent.Parent, "utility")
17
- local readMessage = _utility.readMessage
18
- local writeMessage = _utility.writeMessage
12
+ local Relayer = TS.import(script, script.Parent.Parent, "relayer").Relayer
13
+ local Serdes = TS.import(script, script.Parent.Parent, "serdes").Serdes
14
+ local readMessage = TS.import(script, script.Parent.Parent, "utility").readMessage
19
15
  if setLuneContext == nil then
20
16
  setLuneContext = function() end
21
17
  end
@@ -28,26 +24,6 @@ local defaultMesssageEmitterOptions = {
28
24
  batchRate = 1 / 24,
29
25
  doNotBatch = {},
30
26
  }
31
- local sendMessage
32
- do
33
- local name = "sendMessage"
34
- local existing = ReplicatedStorage:FindFirstChild(name)
35
- local remote = (existing or Instance.new("RemoteEvent", ReplicatedStorage))
36
- if existing == nil then
37
- remote.Name = name
38
- end
39
- sendMessage = remote
40
- end
41
- local sendUnreliableMessage
42
- do
43
- local name = "unreliableMessage"
44
- local existing = ReplicatedStorage:FindFirstChild(name)
45
- local remote = (existing or Instance.new("UnreliableRemoteEvent", ReplicatedStorage))
46
- if existing == nil then
47
- remote.Name = name
48
- end
49
- sendUnreliableMessage = remote
50
- end
51
27
  local MessageEmitter
52
28
  do
53
29
  local super = Destroyable
@@ -71,21 +47,20 @@ do
71
47
  self.server = ServerEmitter.new(self)
72
48
  self.client = ClientEmitter.new(self)
73
49
  self.middleware = MiddlewareProvider.new()
50
+ self.relayer = Relayer.new(self)
51
+ self.serdes = Serdes.new()
74
52
  self.clientCallbacks = {}
75
53
  self.clientFunctions = {}
76
54
  self.serverCallbacks = {}
77
55
  self.serverFunctions = {}
78
56
  self.guards = {}
79
- self.serializers = {}
80
- self.serverQueue = {}
81
- self.clientBroadcastQueue = {}
82
- self.clientQueue = {}
83
57
  self.trash:add(function()
84
58
  self.clientCallbacks = {}
85
59
  self.serverCallbacks = {}
86
60
  self.clientFunctions = {}
87
61
  self.clientCallbacks = {}
88
- self.serializers = {}
62
+ self.serdes.serializers = {}
63
+ self.relayer:relayAll()
89
64
  setmetatable(self, nil)
90
65
  end)
91
66
  end
@@ -93,8 +68,9 @@ do
93
68
  local emitter = MessageEmitter.new(Object.assign({}, defaultMesssageEmitterOptions, options))
94
69
  if meta == nil then
95
70
  warn("[tether::warning] Failed to generate message metadata - make sure you have the Flamework transformer and are using Flamework macro-friendly types in your schemas")
96
- return emitter:initialize()
71
+ return emitter
97
72
  end
73
+ -- lore
98
74
  -- https://discord.com/channels/476080952636997633/506983834877689856/1363938149486821577
99
75
  for kind, _binding in pairs(meta) do
100
76
  local guard = _binding.guard
@@ -104,17 +80,9 @@ do
104
80
  if serializerMetadata == nil then
105
81
  continue
106
82
  end
107
- emitter:addSerializer(numberKind, serializerMetadata)
108
- end
109
- return emitter:initialize()
110
- end
111
- function MessageEmitter:queueMessage(context, message, data)
112
- local queue = if context == "client" then self.clientQueue elseif context == true then self.clientBroadcastQueue else self.serverQueue
113
- local _data = data
114
- table.insert(queue, _data)
115
- if not self:shouldBatch(message) then
116
- self:relay()
83
+ emitter.serdes:addSerializer(numberKind, serializerMetadata)
117
84
  end
85
+ return emitter
118
86
  end
119
87
  function MessageEmitter:runClientMiddlewares(message, data, player)
120
88
  if not self:validateData(message, data) then
@@ -125,7 +93,7 @@ do
125
93
  message = message,
126
94
  data = data,
127
95
  getRawData = function()
128
- return self:getPacket(message, data)
96
+ return self.serdes:serializePacket(message, data)
129
97
  end,
130
98
  }
131
99
  for _, globalMiddleware in self.middleware:getClientGlobal() do
@@ -161,7 +129,7 @@ do
161
129
  message = message,
162
130
  data = data,
163
131
  getRawData = function()
164
- return self:getPacket(message, data)
132
+ return self.serdes:serializePacket(message, data)
165
133
  end,
166
134
  }
167
135
  for _, globalMiddleware in self.middleware:getServerGlobal() do
@@ -189,275 +157,14 @@ do
189
157
  end
190
158
  return { false, ctx.data }
191
159
  end
192
- function MessageEmitter:initialize()
193
- setLuneContext("client")
194
- if RunService:IsClient() then
195
- self.trash:add(sendMessage.OnClientEvent:Connect(function(...)
196
- local serializedPacket = { ... }
197
- return self:onRemoteFire(false, serializedPacket)
198
- end))
199
- self.trash:add(sendUnreliableMessage.OnClientEvent:Connect(function(...)
200
- local serializedPacket = { ... }
201
- return self:onRemoteFire(false, serializedPacket)
202
- end))
203
- end
204
- setLuneContext("server")
205
- if RunService:IsServer() then
206
- self.trash:add(sendMessage.OnServerEvent:Connect(function(player, ...)
207
- local serializedPacket = { ... }
208
- return self:onRemoteFire(true, serializedPacket, player)
209
- end))
210
- self.trash:add(sendUnreliableMessage.OnServerEvent:Connect(function(player, ...)
211
- local serializedPacket = { ... }
212
- return self:onRemoteFire(true, serializedPacket, player)
213
- end))
214
- end
215
- local elapsed = 0
216
- local _binding = self.options
217
- local batchRemotes = _binding.batchRemotes
218
- local batchRate = _binding.batchRate
219
- if not batchRemotes then
220
- return self
221
- end
222
- self.trash:add(RunService.Heartbeat:Connect(function(dt)
223
- elapsed += dt
224
- if elapsed < batchRate then
225
- return nil
226
- end
227
- elapsed -= batchRate
228
- self:relay()
229
- end))
230
- return self
231
- end
232
- function MessageEmitter:relay()
233
- local getPacket = function(info)
234
- return info.packet
235
- end
236
- if RunService:IsClient() then
237
- if #self.serverQueue == 0 then
238
- return nil
239
- end
240
- local _exp = self.serverQueue
241
- -- ▼ ReadonlyArray.map ▼
242
- local _newValue = table.create(#_exp)
243
- local _callback = function(_param)
244
- local message = _param[1]
245
- local data = _param[2]
246
- local unreliable = _param[3]
247
- local packet = self:getPacket(message, data)
248
- return {
249
- packet = packet,
250
- unreliable = unreliable,
251
- }
252
- end
253
- for _k, _v in _exp do
254
- _newValue[_k] = _callback(_v, _k - 1, _exp)
255
- end
256
- -- ▲ ReadonlyArray.map ▲
257
- local serverPacketInfos = _newValue
258
- -- ▼ ReadonlyArray.filter ▼
259
- local _newValue_1 = {}
260
- local _callback_1 = function(info)
261
- return info.unreliable
262
- end
263
- local _length = 0
264
- for _k, _v in serverPacketInfos do
265
- if _callback_1(_v, _k - 1, serverPacketInfos) == true then
266
- _length += 1
267
- _newValue_1[_length] = _v
268
- end
269
- end
270
- -- ▲ ReadonlyArray.filter ▲
271
- -- ▼ ReadonlyArray.map ▼
272
- local _newValue_2 = table.create(#_newValue_1)
273
- for _k, _v in _newValue_1 do
274
- _newValue_2[_k] = getPacket(_v, _k - 1, _newValue_1)
275
- end
276
- -- ▲ ReadonlyArray.map ▲
277
- local unreliableServerPackets = _newValue_2
278
- -- ▼ ReadonlyArray.filter ▼
279
- local _newValue_3 = {}
280
- local _callback_2 = function(info)
281
- return not info.unreliable
282
- end
283
- local _length_1 = 0
284
- for _k, _v in serverPacketInfos do
285
- if _callback_2(_v, _k - 1, serverPacketInfos) == true then
286
- _length_1 += 1
287
- _newValue_3[_length_1] = _v
288
- end
289
- end
290
- -- ▲ ReadonlyArray.filter ▲
291
- -- ▼ ReadonlyArray.map ▼
292
- local _newValue_4 = table.create(#_newValue_3)
293
- for _k, _v in _newValue_3 do
294
- _newValue_4[_k] = getPacket(_v, _k - 1, _newValue_3)
295
- end
296
- -- ▲ ReadonlyArray.map ▲
297
- local serverPackets = _newValue_4
298
- if not (#unreliableServerPackets == 0) then
299
- sendUnreliableMessage:FireServer(unpack(unreliableServerPackets))
300
- end
301
- if not (#serverPackets == 0) then
302
- sendMessage:FireServer(unpack(serverPackets))
303
- end
304
- self.serverQueue = {}
305
- return nil
306
- end
307
- local clientPackets = {}
308
- local addClientPacket = function(player, packetInfo)
309
- local _player = player
310
- local _condition = clientPackets[_player]
311
- if _condition == nil then
312
- _condition = {}
313
- end
314
- local packetList = _condition
315
- local _packetInfo = packetInfo
316
- table.insert(packetList, _packetInfo)
317
- local _player_1 = player
318
- clientPackets[_player_1] = packetList
319
- end
320
- for _, _binding in self.clientQueue do
321
- local player = _binding[1]
322
- local message = _binding[2]
323
- local data = _binding[3]
324
- local unreliable = _binding[4]
325
- local packet = self:getPacket(message, data)
326
- local info = {
327
- packet = packet,
328
- unreliable = unreliable,
329
- }
330
- if typeof(player) == "Instance" then
331
- addClientPacket(player, info)
332
- else
333
- for _1, p in player do
334
- addClientPacket(p, info)
335
- end
336
- end
337
- end
338
- if not (#self.clientBroadcastQueue == 0) then
339
- local _exp = self.clientBroadcastQueue
340
- -- ▼ ReadonlyArray.map ▼
341
- local _newValue = table.create(#_exp)
342
- local _callback = function(_param)
343
- local message = _param[1]
344
- local data = _param[2]
345
- local unreliable = _param[3]
346
- local packet = self:getPacket(message, data)
347
- return {
348
- packet = packet,
349
- unreliable = unreliable,
350
- }
351
- end
352
- for _k, _v in _exp do
353
- _newValue[_k] = _callback(_v, _k - 1, _exp)
354
- end
355
- -- ▲ ReadonlyArray.map ▲
356
- local clientBroadcastPackets = _newValue
357
- -- ▼ ReadonlyArray.filter ▼
358
- local _newValue_1 = {}
359
- local _callback_1 = function(info)
360
- return info.unreliable
361
- end
362
- local _length = 0
363
- for _k, _v in clientBroadcastPackets do
364
- if _callback_1(_v, _k - 1, clientBroadcastPackets) == true then
365
- _length += 1
366
- _newValue_1[_length] = _v
367
- end
368
- end
369
- -- ▲ ReadonlyArray.filter ▲
370
- -- ▼ ReadonlyArray.map ▼
371
- local _newValue_2 = table.create(#_newValue_1)
372
- for _k, _v in _newValue_1 do
373
- _newValue_2[_k] = getPacket(_v, _k - 1, _newValue_1)
374
- end
375
- -- ▲ ReadonlyArray.map ▲
376
- local unreliableBroadcastPackets = _newValue_2
377
- -- ▼ ReadonlyArray.filter ▼
378
- local _newValue_3 = {}
379
- local _callback_2 = function(info)
380
- return not info.unreliable
381
- end
382
- local _length_1 = 0
383
- for _k, _v in clientBroadcastPackets do
384
- if _callback_2(_v, _k - 1, clientBroadcastPackets) == true then
385
- _length_1 += 1
386
- _newValue_3[_length_1] = _v
387
- end
388
- end
389
- -- ▲ ReadonlyArray.filter ▲
390
- -- ▼ ReadonlyArray.map ▼
391
- local _newValue_4 = table.create(#_newValue_3)
392
- for _k, _v in _newValue_3 do
393
- _newValue_4[_k] = getPacket(_v, _k - 1, _newValue_3)
394
- end
395
- -- ▲ ReadonlyArray.map ▲
396
- local broadcastPackets = _newValue_4
397
- if not (#unreliableBroadcastPackets == 0) then
398
- sendUnreliableMessage:FireAllClients(unpack(unreliableBroadcastPackets))
399
- end
400
- if not (#broadcastPackets == 0) then
401
- sendMessage:FireAllClients(unpack(broadcastPackets))
402
- end
403
- self.clientBroadcastQueue = {}
404
- end
405
- if not (#self.clientQueue == 0) then
406
- for player, packetInfo in clientPackets do
407
- if #packetInfo == 0 then
408
- continue
409
- end
410
- if #packetInfo == 0 then
411
- continue
412
- end
413
- -- ▼ ReadonlyArray.filter ▼
414
- local _newValue = {}
415
- local _callback = function(info)
416
- return info.unreliable
417
- end
418
- local _length = 0
419
- for _k, _v in packetInfo do
420
- if _callback(_v, _k - 1, packetInfo) == true then
421
- _length += 1
422
- _newValue[_length] = _v
423
- end
424
- end
425
- -- ▲ ReadonlyArray.filter ▲
426
- -- ▼ ReadonlyArray.map ▼
427
- local _newValue_1 = table.create(#_newValue)
428
- for _k, _v in _newValue do
429
- _newValue_1[_k] = getPacket(_v, _k - 1, _newValue)
430
- end
431
- -- ▲ ReadonlyArray.map ▲
432
- local unreliablePackets = _newValue_1
433
- -- ▼ ReadonlyArray.filter ▼
434
- local _newValue_2 = {}
435
- local _callback_1 = function(info)
436
- return not info.unreliable
437
- end
438
- local _length_1 = 0
439
- for _k, _v in packetInfo do
440
- if _callback_1(_v, _k - 1, packetInfo) == true then
441
- _length_1 += 1
442
- _newValue_2[_length_1] = _v
443
- end
444
- end
445
- -- ▲ ReadonlyArray.filter ▲
446
- -- ▼ ReadonlyArray.map ▼
447
- local _newValue_3 = table.create(#_newValue_2)
448
- for _k, _v in _newValue_2 do
449
- _newValue_3[_k] = getPacket(_v, _k - 1, _newValue_2)
450
- end
451
- -- ▲ ReadonlyArray.map ▲
452
- local packets = _newValue_3
453
- if not (#unreliablePackets == 0) then
454
- sendUnreliableMessage:FireClient(player, unpack(unreliablePackets))
455
- end
456
- if not (#packets == 0) then
457
- sendMessage:FireClient(player, unpack(packets))
458
- end
160
+ function MessageEmitter:onRemoteFire(isServer, serializedPackets, player)
161
+ for _, packet in serializedPackets do
162
+ if buffer.len(packet.messageBuf) > 1 then
163
+ return warn("[tether::warning] Rejected packet because message buffer was larger than one byte")
459
164
  end
460
- self.clientQueue = {}
165
+ local message = readMessage(packet)
166
+ self:executeEventCallbacks(isServer, message, packet, player)
167
+ self:executeFunctions(isServer, message, packet)
461
168
  end
462
169
  end
463
170
  function MessageEmitter:validateData(message, data, requestDropReason)
@@ -474,16 +181,6 @@ do
474
181
  end
475
182
  return guardPassed
476
183
  end
477
- function MessageEmitter:onRemoteFire(isServer, serializedPackets, player)
478
- for _, packet in serializedPackets do
479
- if buffer.len(packet.messageBuf) > 1 then
480
- return warn("[tether::warning] Rejected packet because message buffer was larger than one byte")
481
- end
482
- local message = readMessage(packet)
483
- self:executeEventCallbacks(isServer, message, packet, player)
484
- self:executeFunctions(isServer, message, packet)
485
- end
486
- end
487
184
  function MessageEmitter:executeFunctions(isServer, message, serializedPacket)
488
185
  local functionsMap = if isServer then self.serverFunctions else self.clientFunctions
489
186
  local _message = message
@@ -506,59 +203,19 @@ do
506
203
  local packet = self:deserializeAndValidate(message, serializedPacket)
507
204
  for callback in callbacks do
508
205
  if isServer then
206
+ local _arg0 = player ~= nil
207
+ assert(_arg0)
509
208
  callback(player, packet)
510
209
  else
511
210
  callback(packet)
512
211
  end
513
212
  end
514
213
  end
515
- function MessageEmitter:shouldBatch(message)
516
- local _condition = self.options.batchRemotes
517
- if _condition then
518
- local _doNotBatch = self.options.doNotBatch
519
- local _message = message
520
- _condition = not (_doNotBatch[_message] ~= nil)
521
- end
522
- return _condition
523
- end
524
214
  function MessageEmitter:deserializeAndValidate(message, serializedPacket)
525
- local serializer = self:getSerializer(message)
526
- local _packet = serializer
527
- if _packet ~= nil then
528
- _packet = _packet.deserialize(serializedPacket)
529
- end
530
- local packet = _packet
215
+ local packet = self.serdes:deserializePacket(message, serializedPacket)
531
216
  self:validateData(message, packet)
532
217
  return packet
533
218
  end
534
- function MessageEmitter:getPacket(message, data)
535
- local serializer = self:getSerializer(message)
536
- local messageBuf = buffer.create(1)
537
- writeMessage(messageBuf, message)
538
- if serializer == nil then
539
- return {
540
- messageBuf = messageBuf,
541
- buf = buffer.create(0),
542
- blobs = {},
543
- }
544
- end
545
- local _object = {
546
- messageBuf = messageBuf,
547
- }
548
- for _k, _v in serializer.serialize(data) do
549
- _object[_k] = _v
550
- end
551
- return _object
552
- end
553
- function MessageEmitter:addSerializer(message, meta)
554
- self.serializers[message] = self:createMessageSerializer(meta)
555
- end
556
- function MessageEmitter:createMessageSerializer(meta)
557
- return createSerializer(meta)
558
- end
559
- function MessageEmitter:getSerializer(message)
560
- return self.serializers[message]
561
- end
562
219
  end
563
220
  return {
564
221
  MessageEmitter = MessageEmitter,
@@ -2,6 +2,9 @@
2
2
  local TS = _G[script]
3
3
  local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
4
4
  local ContextualEmitter = TS.import(script, script.Parent, "contextual-emitter").ContextualEmitter
5
+ if setLuneContext == nil then
6
+ setLuneContext = function() end
7
+ end
5
8
  local ServerEmitter
6
9
  do
7
10
  local super = ContextualEmitter
@@ -34,7 +37,7 @@ do
34
37
  if dropRequest then
35
38
  return nil
36
39
  end
37
- self.master:queueMessage(self.context, message, { message, newData, unreliable })
40
+ self.master.relayer:queueMessage(self.context, message, { message, newData, unreliable })
38
41
  end)
39
42
  end
40
43
  function ServerEmitter:setCallback(message, returnMessage, callback)
@@ -0,0 +1,16 @@
1
+ import type { BaseMessage } from "./structs";
2
+ import type { MessageEmitter } from "./emitters/message-emitter";
3
+ export type ServerQueuedMessageData<MessageData> = [keyof MessageData & BaseMessage, MessageData[keyof MessageData], boolean];
4
+ export type ClientQueuedMessageData<MessageData> = [Player | Player[], ...ServerQueuedMessageData<MessageData>];
5
+ export type QueuedMessageData<MessageData> = ClientQueuedMessageData<MessageData> | ServerQueuedMessageData<MessageData>;
6
+ export declare class Relayer<MessageData> {
7
+ private readonly emitter;
8
+ private serverQueue;
9
+ private clientBroadcastQueue;
10
+ private clientQueue;
11
+ constructor(emitter: MessageEmitter<MessageData>);
12
+ queueMessage<K extends keyof MessageData>(context: "client" | "server" | true, message: K & BaseMessage, data: QueuedMessageData<MessageData>): void;
13
+ /** Send all queued data across the network simultaneously */
14
+ relayAll(): void;
15
+ private relay;
16
+ }
@@ -0,0 +1,214 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
4
+ local ReplicatedStorage = _services.ReplicatedStorage
5
+ local RunService = _services.RunService
6
+ local _utility = TS.import(script, script.Parent, "utility")
7
+ local getAllPacketsWhich = _utility.getAllPacketsWhich
8
+ local isReliable = _utility.isReliable
9
+ local isUnreliable = _utility.isUnreliable
10
+ local shouldBatch = _utility.shouldBatch
11
+ if setLuneContext == nil then
12
+ setLuneContext = function() end
13
+ end
14
+ local sendMessage
15
+ do
16
+ local name = "sendMessage"
17
+ local existing = ReplicatedStorage:FindFirstChild(name)
18
+ local remote = (existing or Instance.new("RemoteEvent", ReplicatedStorage))
19
+ if existing == nil then
20
+ remote.Name = name
21
+ end
22
+ sendMessage = remote
23
+ end
24
+ local sendUnreliableMessage
25
+ do
26
+ local name = "unreliableMessage"
27
+ local existing = ReplicatedStorage:FindFirstChild(name)
28
+ local remote = (existing or Instance.new("UnreliableRemoteEvent", ReplicatedStorage))
29
+ if existing == nil then
30
+ remote.Name = name
31
+ end
32
+ sendUnreliableMessage = remote
33
+ end
34
+ local Relayer
35
+ do
36
+ Relayer = setmetatable({}, {
37
+ __tostring = function()
38
+ return "Relayer"
39
+ end,
40
+ })
41
+ Relayer.__index = Relayer
42
+ function Relayer.new(...)
43
+ local self = setmetatable({}, Relayer)
44
+ return self:constructor(...) or self
45
+ end
46
+ function Relayer:constructor(emitter)
47
+ self.emitter = emitter
48
+ self.serverQueue = {}
49
+ self.clientBroadcastQueue = {}
50
+ self.clientQueue = {}
51
+ setLuneContext("client")
52
+ if RunService:IsClient() then
53
+ self.emitter.trash:add(sendMessage.OnClientEvent:Connect(function(...)
54
+ local serializedPacket = { ... }
55
+ return self.emitter:onRemoteFire(false, serializedPacket)
56
+ end))
57
+ self.emitter.trash:add(sendUnreliableMessage.OnClientEvent:Connect(function(...)
58
+ local serializedPacket = { ... }
59
+ return self.emitter:onRemoteFire(false, serializedPacket)
60
+ end))
61
+ end
62
+ setLuneContext("server")
63
+ if RunService:IsServer() then
64
+ self.emitter.trash:add(sendMessage.OnServerEvent:Connect(function(player, ...)
65
+ local serializedPacket = { ... }
66
+ return self.emitter:onRemoteFire(true, serializedPacket, player)
67
+ end))
68
+ self.emitter.trash:add(sendUnreliableMessage.OnServerEvent:Connect(function(player, ...)
69
+ local serializedPacket = { ... }
70
+ return self.emitter:onRemoteFire(true, serializedPacket, player)
71
+ end))
72
+ end
73
+ local elapsed = 0
74
+ local _binding = self.emitter.options
75
+ local batchRemotes = _binding.batchRemotes
76
+ local batchRate = _binding.batchRate
77
+ if not batchRemotes then
78
+ return self
79
+ end
80
+ self.emitter.trash:add(RunService.Heartbeat:Connect(function(dt)
81
+ elapsed += dt
82
+ if elapsed < batchRate then
83
+ return nil
84
+ end
85
+ elapsed -= batchRate
86
+ self:relayAll()
87
+ end))
88
+ end
89
+ function Relayer:queueMessage(context, message, data)
90
+ local queue = if context == "client" then self.clientQueue elseif context == true then self.clientBroadcastQueue else self.serverQueue
91
+ local _data = data
92
+ table.insert(queue, _data)
93
+ if not shouldBatch(message, self.emitter.options) then
94
+ self:relayAll()
95
+ end
96
+ end
97
+ function Relayer:relayAll()
98
+ if RunService:IsClient() then
99
+ return self:relay(function(...)
100
+ local packets = { ... }
101
+ return sendMessage:FireServer(unpack(packets))
102
+ end, function(...)
103
+ local packets = { ... }
104
+ return sendUnreliableMessage:FireServer(unpack(packets))
105
+ end, self.serverQueue, function()
106
+ self.serverQueue = {}
107
+ return self.serverQueue
108
+ end)
109
+ end
110
+ self:relay(function(...)
111
+ local packets = { ... }
112
+ return sendMessage:FireAllClients(unpack(packets))
113
+ end, function(...)
114
+ local packets = { ... }
115
+ return sendUnreliableMessage:FireAllClients(unpack(packets))
116
+ end, self.clientBroadcastQueue, function()
117
+ self.clientBroadcastQueue = {}
118
+ return self.clientBroadcastQueue
119
+ end)
120
+ local playerPacketInfos = {}
121
+ local addClientPacket = function(player, packetInfo)
122
+ local _player = player
123
+ local _condition = playerPacketInfos[_player]
124
+ if _condition == nil then
125
+ _condition = {}
126
+ end
127
+ local packetInfos = _condition
128
+ local _packetInfo = packetInfo
129
+ table.insert(packetInfos, _packetInfo)
130
+ local _player_1 = player
131
+ playerPacketInfos[_player_1] = packetInfos
132
+ end
133
+ for _, _binding in self.clientQueue do
134
+ local player = _binding[1]
135
+ local message = _binding[2]
136
+ local data = _binding[3]
137
+ local unreliable = _binding[4]
138
+ local packet = self.emitter.serdes:serializePacket(message, data)
139
+ local info = {
140
+ packet = packet,
141
+ unreliable = unreliable,
142
+ }
143
+ if typeof(player) == "Instance" then
144
+ addClientPacket(player, info)
145
+ else
146
+ for _1, p in player do
147
+ addClientPacket(p, info)
148
+ end
149
+ end
150
+ end
151
+ if not (#self.clientQueue == 0) then
152
+ for player, packetInfos in playerPacketInfos do
153
+ if #packetInfos == 0 then
154
+ continue
155
+ end
156
+ local unreliablePackets = getAllPacketsWhich(packetInfos, isUnreliable)
157
+ local packets = getAllPacketsWhich(packetInfos, isReliable)
158
+ if not (#unreliablePackets == 0) then
159
+ sendUnreliableMessage:FireClient(player, unpack(unreliablePackets))
160
+ end
161
+ if not (#packets == 0) then
162
+ sendMessage:FireClient(player, unpack(packets))
163
+ end
164
+ end
165
+ self.clientQueue = {}
166
+ end
167
+ end
168
+ function Relayer:relay(send, sendUnreliable, queue, clearQueue)
169
+ if #queue == 0 then
170
+ return nil
171
+ end
172
+ -- ▼ ReadonlyArray.map ▼
173
+ local _newValue = table.create(#queue)
174
+ local _callback = function(messageData)
175
+ local message
176
+ local data
177
+ local unreliable
178
+ local _arg0 = messageData[1]
179
+ if typeof(_arg0) == "Instance" then
180
+ local _binding = messageData
181
+ message = _binding[2]
182
+ data = _binding[3]
183
+ unreliable = _binding[4]
184
+ else
185
+ local _binding = messageData
186
+ message = _binding[1]
187
+ data = _binding[2]
188
+ unreliable = _binding[3]
189
+ end
190
+ local packet = self.emitter.serdes:serializePacket(message, data)
191
+ return {
192
+ packet = packet,
193
+ unreliable = unreliable,
194
+ }
195
+ end
196
+ for _k, _v in queue do
197
+ _newValue[_k] = _callback(_v, _k - 1, queue)
198
+ end
199
+ -- ▲ ReadonlyArray.map ▲
200
+ local packetInfos = _newValue
201
+ local unreliablePackets = getAllPacketsWhich(packetInfos, isUnreliable)
202
+ local packets = getAllPacketsWhich(packetInfos, isReliable)
203
+ if not (#unreliablePackets == 0) then
204
+ sendUnreliable(unpack(unreliablePackets))
205
+ end
206
+ if not (#packets == 0) then
207
+ send(unpack(packets))
208
+ end
209
+ clearQueue()
210
+ end
211
+ end
212
+ return {
213
+ Relayer = Relayer,
214
+ }
@@ -0,0 +1,13 @@
1
+ import type { Modding } from "@flamework/core";
2
+ import type { Serializer, SerializerMetadata } from "@rbxts/serio";
3
+ import type { BaseMessage, SerializedPacket } from "./structs";
4
+ export declare class Serdes<MessageData> {
5
+ serializers: Partial<Record<keyof MessageData, Serializer<MessageData[keyof MessageData]>>>;
6
+ serializePacket<Kind extends keyof MessageData>(message: Kind & BaseMessage, data?: MessageData[Kind]): SerializedPacket;
7
+ deserializePacket<K extends keyof MessageData>(message: K & BaseMessage, serializedPacket: SerializedPacket): MessageData[K] | undefined;
8
+ /** @metadata macro */
9
+ addSerializer<K extends keyof MessageData>(message: K & BaseMessage, meta?: Modding.Many<SerializerMetadata<MessageData[K]>>): void;
10
+ /** @metadata macro */
11
+ createMessageSerializer<Kind extends keyof MessageData>(meta?: Modding.Many<SerializerMetadata<MessageData[Kind]>>): Serializer<MessageData[Kind]>;
12
+ getSerializer<Kind extends keyof MessageData>(message: Kind & BaseMessage): Serializer<MessageData[Kind] | undefined> | undefined;
13
+ }
@@ -0,0 +1,58 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local createSerializer = TS.import(script, TS.getModule(script, "@rbxts", "serio").out).default
4
+ local createMessageBuffer = TS.import(script, script.Parent, "utility").createMessageBuffer
5
+ local Serdes
6
+ do
7
+ Serdes = setmetatable({}, {
8
+ __tostring = function()
9
+ return "Serdes"
10
+ end,
11
+ })
12
+ Serdes.__index = Serdes
13
+ function Serdes.new(...)
14
+ local self = setmetatable({}, Serdes)
15
+ return self:constructor(...) or self
16
+ end
17
+ function Serdes:constructor()
18
+ self.serializers = {}
19
+ end
20
+ function Serdes:serializePacket(message, data)
21
+ local serializer = self:getSerializer(message)
22
+ local messageBuf = createMessageBuffer(message)
23
+ if serializer == nil then
24
+ return {
25
+ messageBuf = messageBuf,
26
+ buf = buffer.create(0),
27
+ blobs = {},
28
+ }
29
+ end
30
+ local _object = {
31
+ messageBuf = messageBuf,
32
+ }
33
+ for _k, _v in serializer.serialize(data) do
34
+ _object[_k] = _v
35
+ end
36
+ return _object
37
+ end
38
+ function Serdes:deserializePacket(message, serializedPacket)
39
+ local serializer = self:getSerializer(message)
40
+ local _result = serializer
41
+ if _result ~= nil then
42
+ _result = _result.deserialize(serializedPacket)
43
+ end
44
+ return _result
45
+ end
46
+ function Serdes:addSerializer(message, meta)
47
+ self.serializers[message] = self:createMessageSerializer(meta)
48
+ end
49
+ function Serdes:createMessageSerializer(meta)
50
+ return createSerializer(meta)
51
+ end
52
+ function Serdes:getSerializer(message)
53
+ return self.serializers[message]
54
+ end
55
+ end
56
+ return {
57
+ Serdes = Serdes,
58
+ }
package/out/structs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Modding } from "@flamework/core";
2
- import type { SerializerMetadata, SerializedData, Transform, Vector, String, u8, u16, u24, u32, i8, i16, i24, i32, f16, f24, f32, f64 } from "@rbxts/serio";
2
+ import type { SerializerMetadata, SerializedData, SchemaGuard } from "@rbxts/serio";
3
3
  export type MessageCallback<T = unknown> = ServerMessageCallback<T> | ClientMessageCallback<T>;
4
4
  export type FunctionMessageCallback<T = unknown, R = unknown> = ServerFunctionMessageCallback<T, R> | ClientFunctionMessageCallback<T, R>;
5
5
  export type ClientMessageCallback<T = unknown> = (data: T) => void;
@@ -15,36 +15,11 @@ export interface SerializedPacket extends SerializedData {
15
15
  readonly messageBuf: buffer;
16
16
  }
17
17
  export type MessageEvent = (...packets: SerializedPacket[]) => void;
18
- type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
19
- type ReplaceByMapWithDepth<T, Depth extends number = 24> = [
20
- Depth
21
- ] extends [never] ? T : T extends Callback ? T : T extends Vector ? Vector3 : T extends Transform ? CFrame : T extends String ? string : T extends buffer ? buffer : T extends {
22
- _packed: [infer V];
23
- } ? ReplaceByMapWithDepth<V, Prev[Depth]> : T extends {
24
- _list: [infer V];
25
- } ? ReplaceByMapWithDepth<V, Depth> : T extends {
26
- _tuple: [infer A];
27
- } ? ReplaceByMapWithDepth<A, Prev[Depth]> : T extends {
28
- _set: [infer V];
29
- } ? Set<ReplaceByMapWithDepth<V, Prev[Depth]>> : T extends {
30
- _set: infer T extends unknown[];
31
- } ? Map<ReplaceByMapWithDepth<T[number], Prev[Depth]>, ReplaceByMapWithDepth<T[number], Prev[Depth]>> : T extends {
32
- _map: [infer K, infer V];
33
- } ? Map<ReplaceByMapWithDepth<K, Prev[Depth]>, ReplaceByMapWithDepth<V, Prev[Depth]>> : T extends {
34
- _map: [infer V];
35
- } ? Map<ReplaceByMapWithDepth<V, Prev[Depth]>, ReplaceByMapWithDepth<V, Prev[Depth]>> : T extends u8 | u16 | u24 | u32 | i8 | i16 | i24 | i32 | f16 | f24 | f32 | f64 ? number : T extends Color3 ? {
36
- R: number;
37
- G: number;
38
- B: number;
39
- } : T extends any[] ? ReplaceByMapWithDepth<T[number], Prev[Depth]>[] : T extends ReadonlyMap<unknown, unknown> ? T : T extends object ? {
40
- [K in keyof T]: ReplaceByMapWithDepth<T[K], Prev[Depth]>;
41
- } : T;
42
18
  export interface MessageMetadata<MessageData, Kind extends keyof MessageData> {
43
- readonly guard: Modding.Generic<ReplaceByMapWithDepth<MessageData[Kind]>, "guard">;
19
+ readonly guard: SchemaGuard<MessageData[Kind]>;
44
20
  readonly serializerMetadata: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<MessageData[Kind]>>;
45
21
  }
46
22
  export type Guard<T = unknown> = (value: unknown) => value is T;
47
23
  export type MessageEmitterMetadata<MessageData> = {
48
24
  readonly [Kind in keyof MessageData]: MessageMetadata<MessageData, Kind>;
49
25
  };
50
- export {};
package/out/utility.d.ts CHANGED
@@ -1,6 +1,13 @@
1
- import type { BaseMessage, SerializedPacket } from "./structs";
1
+ import type { BaseMessage, PacketInfo, SerializedPacket } from "./structs";
2
+ import type { MessageEmitterOptions } from "./emitters/message-emitter";
2
3
  export declare function bufferToString(buf?: buffer): string;
3
4
  export declare function encodeMessage(message: BaseMessage): number;
4
5
  export declare function decodeMessage(encoded: number): BaseMessage;
5
6
  export declare function writeMessage(buf: buffer, message: BaseMessage): void;
6
7
  export declare function readMessage(packet: SerializedPacket | buffer): BaseMessage;
8
+ export declare function createMessageBuffer(message: BaseMessage): buffer;
9
+ export declare function getAllPacketsWhich(infos: PacketInfo[], predicate: (info: PacketInfo) => boolean): SerializedPacket[];
10
+ export declare function isUnreliable(info: PacketInfo): boolean;
11
+ export declare function isReliable(info: PacketInfo): boolean;
12
+ export declare function getPacket(info: PacketInfo): SerializedPacket;
13
+ export declare function shouldBatch<MessageData>(message: keyof MessageData & BaseMessage, options: MessageEmitterOptions<MessageData>): boolean;
package/out/utility.luau CHANGED
@@ -23,7 +23,7 @@ local function bufferToString(buf)
23
23
  end
24
24
  end
25
25
  end
26
- table.insert(s, "}")
26
+ table.insert(s, " }")
27
27
  return table.concat(s, "")
28
28
  end
29
29
  local function encodeMessage(message)
@@ -44,10 +44,61 @@ local function readMessage(packet)
44
44
  local buf = if type(_packet) == "buffer" then packet else packet.messageBuf
45
45
  return decodeMessage(buffer.readu8(buf, 0))
46
46
  end
47
+ local function createMessageBuffer(message)
48
+ local messageBuf = buffer.create(1)
49
+ writeMessage(messageBuf, message)
50
+ return messageBuf
51
+ end
52
+ local getPacket
53
+ local function getAllPacketsWhich(infos, predicate)
54
+ local _infos = infos
55
+ local _predicate = predicate
56
+ -- ▼ ReadonlyArray.filter ▼
57
+ local _newValue = {}
58
+ local _length = 0
59
+ for _k, _v in _infos do
60
+ if _predicate(_v, _k - 1, _infos) == true then
61
+ _length += 1
62
+ _newValue[_length] = _v
63
+ end
64
+ end
65
+ -- ▲ ReadonlyArray.filter ▲
66
+ -- ▼ ReadonlyArray.map ▼
67
+ local _newValue_1 = table.create(#_newValue)
68
+ for _k, _v in _newValue do
69
+ _newValue_1[_k] = getPacket(_v, _k - 1, _newValue)
70
+ end
71
+ -- ▲ ReadonlyArray.map ▲
72
+ return _newValue_1
73
+ end
74
+ local function isUnreliable(info)
75
+ return info.unreliable
76
+ end
77
+ local function isReliable(info)
78
+ return not info.unreliable
79
+ end
80
+ function getPacket(info)
81
+ return info.packet
82
+ end
83
+ local function shouldBatch(message, options)
84
+ local _condition = options.batchRemotes
85
+ if _condition then
86
+ local _doNotBatch = options.doNotBatch
87
+ local _message = message
88
+ _condition = not (_doNotBatch[_message] ~= nil)
89
+ end
90
+ return _condition
91
+ end
47
92
  return {
48
93
  bufferToString = bufferToString,
49
94
  encodeMessage = encodeMessage,
50
95
  decodeMessage = decodeMessage,
51
96
  writeMessage = writeMessage,
52
97
  readMessage = readMessage,
98
+ createMessageBuffer = createMessageBuffer,
99
+ getAllPacketsWhich = getAllPacketsWhich,
100
+ isUnreliable = isUnreliable,
101
+ isReliable = isReliable,
102
+ getPacket = getPacket,
103
+ shouldBatch = shouldBatch,
53
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "main": "out/init.luau",
5
5
  "scripts": {
6
6
  "setup-rokit": "rokit trust lune-org/lune && rokit trust rojo-rbx/rojo && rokit install",
@@ -36,7 +36,7 @@
36
36
  "rbxts-transformer-flamework": "^1.3.2",
37
37
  "roblox-ts": "^3.0.0",
38
38
  "ts-toolbelt": "^9.6.0",
39
- "typescript": "^5"
39
+ "typescript": "^5.5.3"
40
40
  },
41
41
  "dependencies": {
42
42
  "@flamework/core": "^1.3.2",
@@ -44,7 +44,7 @@
44
44
  "@rbxts/flamework-meta-utils": "^1.0.7",
45
45
  "@rbxts/object-utils": "^1.0.4",
46
46
  "@rbxts/repr": "^1.0.3",
47
- "@rbxts/serio": "^1.0.19",
47
+ "@rbxts/serio": "^1.1.0",
48
48
  "@rbxts/services": "^1.6.0",
49
49
  "@rbxts/trash": "^1.0.8"
50
50
  }