@rbxts/tether 1.0.5 → 1.0.6

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,7 @@
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.
3
+
4
+ ## Usage
3
5
 
4
6
  ### In `shared/messaging.ts`
5
7
  ```ts
@@ -40,4 +42,46 @@ messaging.emitServer(Message.TEST, {
40
42
  foo: "bar",
41
43
  n: 69
42
44
  });
45
+ ```
46
+
47
+ ## Middleware
48
+
49
+ ### Creating middleware
50
+ ```ts
51
+ import { type Middleware, DropRequest } from "@rbxts/tether";
52
+
53
+ export function rateLimit(interval: number): Middleware {
54
+ let lastRequest = 0;
55
+
56
+ return () => {
57
+ if (os.clock() - lastRequest < interval)
58
+ return DropRequest;
59
+
60
+ lastRequest = os.clock();
61
+ };
62
+ }
63
+ ```
64
+
65
+ ### Using middleware
66
+ ```ts
67
+ import type { DataType } from "@rbxts/flamework-binary-serializer";
68
+ import { MessageEmitter, BuiltinMiddlewares } from "@rbxts/tether";
69
+
70
+ export const messaging = MessageEmitter.create<MessageData>();
71
+ messaging.middleware
72
+ .useUniversal(Message.Test, [BuiltinMiddlewares.rateLimit(5)]) // only allows requests every 5 seconds, drops any
73
+ // requests that occur within 5 seconds of each other
74
+ .useClient(Message.Test, [BuiltinMiddlewares.validateClient()]); // automatically validates that data sent through the remote
75
+ // matches the data associated with the message at runtime
76
+
77
+ export const enum Message {
78
+ Test
79
+ }
80
+
81
+ export interface MessageData {
82
+ [Message.Test]: {
83
+ readonly foo: string;
84
+ readonly n: DataType.u8;
85
+ };
86
+ }
43
87
  ```
@@ -1,4 +1,30 @@
1
- import { Middleware } from "./middleware";
1
+ import { Modding } from "@flamework/core";
2
+ import { type UniversalMiddleware, type ServerMiddleware, type ClientMiddleware } from "./middleware";
3
+ type Guard<T> = (value: unknown) => value is T;
2
4
  export declare namespace BuiltinMiddlewares {
3
- function rateLimit<Data>(interval: number): Middleware<Data>;
5
+ /**
6
+ * Creates a universal middleware that will drop any message that occurs within the given interval of the previous message.
7
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request.
8
+ * @returns A middleware that will drop any message that occurs within the given interval.
9
+ */
10
+ function rateLimit(interval: number): UniversalMiddleware;
11
+ /**
12
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
13
+ * If the guard fails, the middleware will drop the message.
14
+ *
15
+ * @param guard The guard to use to validate the data.
16
+ * @returns A server middleware that validates the data with the given guard.
17
+ * @macro
18
+ */
19
+ function validateServer<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ServerMiddleware<T>;
20
+ /**
21
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
22
+ * If the guard fails, the middleware will drop the message.
23
+ *
24
+ * @param guard The guard to use to validate the data.
25
+ * @returns A server middleware that validates the data with the given guard.
26
+ * @macro
27
+ */
28
+ function validateClient<T>(guard?: Guard<T> | Modding.Generic<T, "guard">): ClientMiddleware<T>;
4
29
  }
30
+ export {};
@@ -1,9 +1,20 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
4
+ local noOp = function() end
5
+ local validationGuardGenerationFailed = function(context)
6
+ return `[Tether]: Failed to generate guard for validate{context}<T> builtin middleware - skipping validation`
7
+ end
4
8
  local BuiltinMiddlewares = {}
5
9
  do
6
10
  local _container = BuiltinMiddlewares
11
+ --[[
12
+ *
13
+ * Creates a universal middleware that will drop any message that occurs within the given interval of the previous message.
14
+ * @param interval The interval in seconds that the middleware should wait before allowing a new request.
15
+ * @returns A middleware that will drop any message that occurs within the given interval.
16
+
17
+ ]]
7
18
  local function rateLimit(interval)
8
19
  local lastRequest = 0
9
20
  return function()
@@ -14,6 +25,52 @@ do
14
25
  end
15
26
  end
16
27
  _container.rateLimit = rateLimit
28
+ --[[
29
+ *
30
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
31
+ * If the guard fails, the middleware will drop the message.
32
+ *
33
+ * @param guard The guard to use to validate the data.
34
+ * @returns A server middleware that validates the data with the given guard.
35
+ * @macro
36
+
37
+ ]]
38
+ local function validateServer(guard)
39
+ if guard == nil then
40
+ warn(validationGuardGenerationFailed("Server"))
41
+ return noOp
42
+ end
43
+ return function(data)
44
+ if guard(data) then
45
+ return nil
46
+ end
47
+ return DropRequest
48
+ end
49
+ end
50
+ _container.validateServer = validateServer
51
+ --[[
52
+ *
53
+ * Creates a server middleware that validates the data with the given guard (or a generated guard if none was provided).
54
+ * If the guard fails, the middleware will drop the message.
55
+ *
56
+ * @param guard The guard to use to validate the data.
57
+ * @returns A server middleware that validates the data with the given guard.
58
+ * @macro
59
+
60
+ ]]
61
+ local function validateClient(guard)
62
+ if guard == nil then
63
+ warn(validationGuardGenerationFailed("Client"))
64
+ return noOp
65
+ end
66
+ return function(data)
67
+ if guard(data) then
68
+ return nil
69
+ end
70
+ return DropRequest
71
+ end
72
+ end
73
+ _container.validateClient = validateClient
17
74
  end
18
75
  return {
19
76
  BuiltinMiddlewares = BuiltinMiddlewares,
package/out/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { MessageEmitter } from "./message-emitter";
2
2
  export { BuiltinMiddlewares } from "./builtin-middlewares";
3
- export { MiddlewareProvider, DropRequest, type ClientMiddleware, type ServerMiddleware, type Middleware } from "./middleware";
3
+ export { DropRequest, type ClientMiddleware, type ServerMiddleware, type Middleware } from "./middleware";
package/out/init.luau CHANGED
@@ -3,7 +3,5 @@ local TS = _G[script]
3
3
  local exports = {}
4
4
  exports.MessageEmitter = TS.import(script, script, "message-emitter").MessageEmitter
5
5
  exports.BuiltinMiddlewares = TS.import(script, script, "builtin-middlewares").BuiltinMiddlewares
6
- local _middleware = TS.import(script, script, "middleware")
7
- exports.MiddlewareProvider = _middleware.MiddlewareProvider
8
- exports.DropRequest = _middleware.DropRequest
6
+ exports.DropRequest = TS.import(script, script, "middleware").DropRequest
9
7
  return exports
@@ -4,23 +4,23 @@ import Destroyable from "@rbxts/destroyable";
4
4
  import { MiddlewareProvider } from "./middleware";
5
5
  import type { ClientMessageCallback, ServerMessageCallback } from "./structs";
6
6
  export declare class MessageEmitter<MessageData> extends Destroyable {
7
- private readonly middleware?;
7
+ readonly middleware: MiddlewareProvider<MessageData>;
8
8
  private readonly clientCallbacks;
9
9
  private readonly serverCallbacks;
10
10
  private serializers;
11
11
  private serverEvents;
12
12
  private clientEvents;
13
13
  /** @metadata macro */
14
- static create<MessageData>(middleware?: MiddlewareProvider<MessageData>, metaForEachMessage?: Modding.Many<{
14
+ static create<MessageData>(metaForEachMessage?: Modding.Many<{
15
15
  [Kind in keyof MessageData]: MessageData[Kind] extends undefined ? undefined : Modding.Many<SerializerMetadata<MessageData[Kind]>>;
16
16
  }>): MessageEmitter<MessageData>;
17
17
  private constructor();
18
18
  /**.
19
- * @returns A destructor function that disconnects the callback from the message
19
+ * @returns A destructor function that disconnects the callback from the message.
20
20
  */
21
21
  onServerMessage<Kind extends keyof MessageData>(message: Kind, callback: ServerMessageCallback<MessageData[Kind]>): () => void;
22
22
  /**.
23
- * @returns A destructor function that disconnects the callback from the message
23
+ * @returns A destructor function that disconnects the callback from the message.
24
24
  */
25
25
  onClientMessage<Kind extends keyof MessageData>(message: Kind, callback: ClientMessageCallback<MessageData[Kind]>): () => void;
26
26
  private on;
@@ -7,7 +7,9 @@ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
7
7
  local Players = _services.Players
8
8
  local RunService = _services.RunService
9
9
  local Destroyable = TS.import(script, TS.getModule(script, "@rbxts", "destroyable").out).default
10
- local DropRequest = TS.import(script, script.Parent, "middleware").DropRequest
10
+ local _middleware = TS.import(script, script.Parent, "middleware")
11
+ local DropRequest = _middleware.DropRequest
12
+ local MiddlewareProvider = _middleware.MiddlewareProvider
11
13
  local GlobalEvents = Networking.createEvent("@rbxts/tether:message-emitter@GlobalEvents")
12
14
  local MessageEmitter
13
15
  do
@@ -23,9 +25,9 @@ do
23
25
  local self = setmetatable({}, MessageEmitter)
24
26
  return self:constructor(...) or self
25
27
  end
26
- function MessageEmitter:constructor(middleware)
28
+ function MessageEmitter:constructor()
27
29
  super.constructor(self)
28
- self.middleware = middleware
30
+ self.middleware = MiddlewareProvider.new()
29
31
  self.clientCallbacks = {}
30
32
  self.serverCallbacks = {}
31
33
  self.serializers = {}
@@ -84,8 +86,8 @@ do
84
86
  })
85
87
  end
86
88
  end
87
- function MessageEmitter:create(middleware, metaForEachMessage)
88
- local emitter = MessageEmitter.new(middleware)
89
+ function MessageEmitter:create(metaForEachMessage)
90
+ local emitter = MessageEmitter.new()
89
91
  if metaForEachMessage == nil then
90
92
  warn("[Tether]: Failed to generate serializer metadata for MessageEmitter")
91
93
  return emitter:initialize()
@@ -2,8 +2,9 @@ type DropRequestSymbol = symbol & {
2
2
  _skip_middleware?: undefined;
3
3
  };
4
4
  export declare const DropRequest: DropRequestSymbol;
5
- export type ClientMiddleware<Data = unknown> = (player: Player, data: Data | undefined) => DropRequestSymbol | void;
6
- export type ServerMiddleware<Data = unknown> = (data: Data | undefined) => DropRequestSymbol | void;
5
+ export type ClientMiddleware<Data = unknown> = (player: Player, data: Readonly<Data> | undefined) => DropRequestSymbol | void;
6
+ export type ServerMiddleware<Data = unknown> = (data: Readonly<Data> | undefined) => DropRequestSymbol | void;
7
+ export type UniversalMiddleware = () => DropRequestSymbol | void;
7
8
  export type Middleware<Data = unknown> = ServerMiddleware<Data> & ClientMiddleware<Data>;
8
9
  export declare class MiddlewareProvider<MessageData> {
9
10
  private readonly clientMiddlewares;
@@ -14,5 +15,6 @@ export declare class MiddlewareProvider<MessageData> {
14
15
  getServer<Kind extends keyof MessageData>(message: Kind): ServerMiddleware<MessageData[Kind]>[];
15
16
  useClient<Kind extends keyof MessageData>(message: Kind, middlewares: ClientMiddleware<MessageData[Kind]> | ClientMiddleware<MessageData[Kind]>[], order?: number): this;
16
17
  useServer<Kind extends keyof MessageData>(message: Kind, middlewares: ServerMiddleware<MessageData[Kind]> | ServerMiddleware<MessageData[Kind]>[], order?: number): this;
18
+ useUniversal<Kind extends keyof MessageData>(message: Kind, middlewares: UniversalMiddleware | UniversalMiddleware[], order?: number): this;
17
19
  }
18
20
  export {};
@@ -1,13 +1,5 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
- local function createSymbol(name)
3
- local symbol = newproxy(true)
4
- local mt = getmetatable(symbol)
5
- mt.__tostring = function()
6
- return name
7
- end
8
- return symbol
9
- end
10
- local DropRequest = createSymbol("DropRequest")
2
+ local DropRequest = newproxy()
11
3
  local MiddlewareProvider
12
4
  do
13
5
  MiddlewareProvider = setmetatable({}, {
@@ -70,6 +62,11 @@ do
70
62
  end
71
63
  return self
72
64
  end
65
+ function MiddlewareProvider:useUniversal(message, middlewares, order)
66
+ self:useClient(message, middlewares, order)
67
+ self:useServer(message, middlewares, order)
68
+ return self
69
+ end
73
70
  end
74
71
  return {
75
72
  DropRequest = DropRequest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/tether",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "main": "out/init.lua",
5
5
  "scripts": {
6
6
  "build": "rbxtsc",