@flamework-experimental/networking 2.0.0-alpha.1 → 2.0.0-alpha.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.
Files changed (44) hide show
  1. package/README.md +35 -27
  2. package/flamework.build +1 -1
  3. package/out/event/createEvent.d.ts +22 -4
  4. package/out/event/createEvent.luau +47 -49
  5. package/out/events/createClientMethod.luau +7 -3
  6. package/out/events/createGenericHandler.luau +6 -3
  7. package/out/events/createNetworkingEvent.luau +7 -5
  8. package/out/events/createServerMethod.d.ts +1 -1
  9. package/out/events/createServerMethod.luau +8 -3
  10. package/out/events/types.d.ts +4 -3
  11. package/out/function/createFunctionReceiver.d.ts +10 -9
  12. package/out/function/createFunctionReceiver.luau +53 -55
  13. package/out/function/createFunctionSender.d.ts +3 -2
  14. package/out/function/createFunctionSender.luau +118 -40
  15. package/out/functions/createClientMethod.d.ts +1 -1
  16. package/out/functions/createClientMethod.luau +9 -8
  17. package/out/functions/createGenericHandler.luau +8 -12
  18. package/out/functions/createNetworkingFunction.luau +7 -5
  19. package/out/functions/createServerMethod.d.ts +1 -1
  20. package/out/functions/createServerMethod.luau +9 -8
  21. package/out/functions/types.d.ts +10 -12
  22. package/out/handlers.d.ts +35 -36
  23. package/out/index.d.ts +6 -0
  24. package/out/init.luau +6 -0
  25. package/out/middleware/createGuards.d.ts +10 -0
  26. package/out/middleware/createGuards.luau +32 -0
  27. package/out/middleware/processor.d.ts +62 -0
  28. package/out/middleware/processor.luau +214 -0
  29. package/out/middleware/types.d.ts +26 -15
  30. package/out/util/createOnce.d.ts +14 -0
  31. package/out/util/createOnce.luau +66 -0
  32. package/out/util/createSignalContainer.d.ts +2 -1
  33. package/out/util/createSignalContainer.luau +2 -2
  34. package/out/util/signal.d.ts +27 -0
  35. package/out/util/signal.luau +135 -0
  36. package/out/util/trimArguments.d.ts +12 -0
  37. package/out/util/trimArguments.luau +48 -0
  38. package/package.json +3 -4
  39. package/out/middleware/createGuardMiddleware.d.ts +0 -6
  40. package/out/middleware/createGuardMiddleware.luau +0 -44
  41. package/out/middleware/createMiddlewareProcessor.d.ts +0 -3
  42. package/out/middleware/createMiddlewareProcessor.luau +0 -24
  43. package/out/util/timeoutPromise.d.ts +0 -1
  44. package/out/util/timeoutPromise.luau +0 -10
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # Flamework
2
2
 
3
- Flamework is an extensible framework for roblox-ts designed around portable, isolated and testable modules.
3
+ Flamework is an extensible framework for roblox-ts. It is built around modules that are portable,
4
+ isolated and easy to test.
4
5
 
5
6
  ## Documentation
6
7
 
7
- **[docs/](docs/README.md)** -- start there. A ten-part guide that builds up from a working entry
8
- point to plugins and project layout, plus reference material:
8
+ Start with **[docs/](docs/README.md)**. It holds a twelve-part guide, which starts from a working
9
+ entry point and builds up to plugins, project layout, scopes and testing. It also holds reference
10
+ material:
9
11
 
10
12
  | | |
11
13
  |---|---|
12
- | [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1. |
14
+ | [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1, scopes, testing in the place. |
13
15
  | [Internals](docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
14
16
  | [Transformer plugins](docs/reference/transformer-plugins.md) | Adding macro types of your own. |
15
17
 
@@ -27,6 +29,7 @@ bun install
27
29
  bun run build # builds every package in dependency order
28
30
  bun run test # build + transformer tests + runtime specs
29
31
  bun run lint
32
+ bun run test:place # the in-place suite in Roblox Studio (tests/place); needs Studio, Rojo and Lune
30
33
  ```
31
34
 
32
35
  ### Packages
@@ -36,31 +39,36 @@ bun run lint
36
39
  | `packages/core` | Modules, dependency injection, plugins and lifecycle events |
37
40
  | `packages/components` | CollectionService components, built on the core plugin system |
38
41
  | `packages/networking` | Remote events and functions |
39
- | `packages/testing` | In-place tests: sections, cleanup, a bindable and a remote to run them, a cloud entry; and `flamework-test`, the CLI that runs them in Roblox Studio on this machine or through Open Cloud (`cli/`) |
42
+ | `packages/testing` | Tests that run inside a place: sections, cleanup, a bindable and a remote that run them, and a cloud entry point. Also `flamework-test` (`cli/`), the CLI that runs them in Roblox Studio on this machine or through Open Cloud |
40
43
  | `packages/transformer` | The roblox-ts transformer |
41
44
  | `packages/transformer-plugin` | Public API for writing transformer plugins |
42
- | `packages/specs` | Runtime specs, compiled by `rbxtsc` and executed under Lune |
45
+ | `packages/specs` | Runtime specs, built by `rbxtsc` and run under Lune |
43
46
 
44
47
  ### Tests
45
48
 
46
- Two suites, both run by `bun run test`:
47
-
48
- - **Transformer tests** (`bun run test:unit`) compile a fixture project with the real `rbxtsc` and
49
- assert on the emitted Luau — guard generation, identifiers, nested macros and the plugin system.
50
- - **Runtime specs** (`bun run test:runtime`) execute compiled `@flamework-experimental/core`, `components` and
51
- `networking` under Lune using the harness in [`tests/runtime`](tests/runtime), which models
52
- roblox-ts's `TS.import` tree over the filesystem and stubs the Roblox API surface Flamework
53
- touches (Instances, attributes, CollectionService, RemoteEvents, Players, signals, `task`,
54
- `Enum`, and a `Heartbeat` pump so `Promise.delay` -- and therefore request timeouts -- runs).
55
- They cover dependency injection, modules, hooks and the per-frame lifecycle events, component
56
- construction, dependencies and streaming, and both halves of networking: events, functions,
57
- middleware and the generated guards.
58
-
59
- They run twice, once as `Server` and once as `Client`, because realm-dependent code paths --
60
- `@Provider`'s metadata, component streaming, and the client/server halves of networking -- differ
61
- between them. Where a spec asserts something realm-specific, running it from both sides is what
62
- proves the two agree: a function receives on `$name` and sends on `@name` from the server and the
63
- mirror image from the client, so the pair of runs pins the wire format down from both ends.
64
-
65
- Specs live in [`packages/specs`](packages/specs) and are compiled by `rbxtsc` like any other
66
- Flamework consumer, so they exercise the transformer and the runtime together.
49
+ `bun run test` runs two suites:
50
+
51
+ - **Transformer tests** (`bun run test:unit`) build a fixture project with the real `rbxtsc` and
52
+ check the Luau it emits: guard generation, identifiers, nested macros and the plugin system.
53
+ - **Runtime specs** (`bun run test:runtime`) run the built `@flamework-experimental/core`,
54
+ `components` and `networking` packages under Lune. The harness in [`tests/runtime`](tests/runtime)
55
+ models roblox-ts's `TS.import` tree over the filesystem. It also stubs the parts of the Roblox API
56
+ that Flamework uses: Instances, attributes, CollectionService, RemoteEvents, Players, signals,
57
+ `task`, `Enum`, and a `Heartbeat` pump so that `Promise.delay` runs (and with it, request
58
+ timeouts). The specs cover dependency injection, modules, hooks and the per-frame lifecycle
59
+ events; component construction, dependencies and streaming; and both halves of networking:
60
+ events, functions, middleware and the generated guards.
61
+
62
+ The specs run twice, once as `Server` and once as `Client`, because some code paths depend on the
63
+ realm: `@Provider`'s metadata, component streaming, and the client and server halves of
64
+ networking. Running a realm-specific spec from both sides proves that the two sides agree. For
65
+ example, from the server a function receives on `$name` and sends on `@name`, and from the client
66
+ it does the reverse, so the two runs pin down the wire format from both ends.
67
+
68
+ Specs live in [`packages/specs`](packages/specs). `rbxtsc` builds them like any other project that
69
+ uses Flamework, so they test the transformer and the runtime together.
70
+
71
+ A third suite runs against the real engine, and `bun run test` leaves it out. It lives in the
72
+ [test place](tests/place/README.md), a small game linked to the packages' builds.
73
+ `bun run test:place` runs its `@flamework-experimental/testing` sections in Roblox Studio, on both
74
+ realms, under four Rojo projects (see [Testing in Roblox Studio](docs/testing/studio.md)).
package/flamework.build CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "flameworkVersion": "2.0.0-alpha.1",
3
+ "flameworkVersion": "2.0.0-alpha.4",
4
4
  "identifiers": {},
5
5
  "idGenerationMode": "full",
6
6
  "identifierPrefix": "$n"
@@ -1,6 +1,8 @@
1
1
  import { Serialization } from "@flamework-experimental/core";
2
- import { MiddlewareFactory, MiddlewareProcessor } from "../middleware/types";
2
+ import { MiddlewareFactory } from "../middleware/types";
3
3
  import { NetworkInfo } from "../types";
4
+ import { Guards } from "../middleware/processor";
5
+ import { SignalConnection } from "../util/signal";
4
6
  export interface CreateEventOptions {
5
7
  /**
6
8
  * The namespace this event should be created in.
@@ -30,6 +32,11 @@ export interface CreateEventOptions {
30
32
  * A list of middleware that this event uses when it receives an event.
31
33
  */
32
34
  incomingMiddleware?: MiddlewareFactory<any[], void>[];
35
+ /**
36
+ * The generated argument checks, run on what arrives before any middleware. A message that fails
37
+ * them is dropped.
38
+ */
39
+ incomingGuards?: Guards;
33
40
  /**
34
41
  * Unpacks the argument list of incoming events. Absent when the project does not enable
35
42
  * serialization. Outgoing lists reach `fire*` already packed: the transformer generates the
@@ -46,14 +53,25 @@ export interface EventInterface {
46
53
  fireServer(...args: unknown[]): void;
47
54
  fireClient(player: Player, ...args: unknown[]): void;
48
55
  fireAllClients(...args: unknown[]): void;
49
- connectServer(callback: (player: Player, ...args: unknown[]) => void): RBXScriptConnection;
50
- connectClient(callback: (...args: unknown[]) => void): RBXScriptConnection;
51
- invoke: MiddlewareProcessor<any[], void>;
56
+ connectServer(callback: (player: Player, ...args: unknown[]) => void): SignalConnection;
57
+ connectClient(callback: (...args: unknown[]) => void): SignalConnection;
58
+ /**
59
+ * Runs the receiving half as if the remote had delivered `args` (plain values, never a payload), on
60
+ * a recycled thread of its own, so that a middleware that yields does not hold up the caller.
61
+ */
62
+ predict(player: Player | undefined, ...args: unknown[]): void;
52
63
  }
53
64
  /**
54
65
  * The argument list a remote delivered: `args` as they are without a decoder, otherwise the buffer
55
66
  * and blob list unpacked. `undefined` when the payload was malformed, after reporting it through
56
67
  * `onMalformed`. Decoding runs under `pcall`: a hostile buffer raises instead of yielding garbage.
68
+ *
69
+ * Either way the list is trimmed after its last value, so that no hop after it loses the values
70
+ * that follow a nil (see `trimArguments`). A decoded list keeps one slot per declared parameter,
71
+ * so an absent trailing optional would otherwise end it in nil.
72
+ *
73
+ * An event's own messages are decoded in the receive pipeline (`processor.luau`); this is for a
74
+ * function's requests and responses, which carry a plain request id ahead of the payload.
57
75
  */
58
76
  export declare function decodeArguments(decoder: Serialization.Decoder | undefined, player: Player | undefined, args: unknown[], onMalformed?: (player: Player | undefined, message: string) => void): unknown[] | undefined;
59
77
  export declare function createEvent(options: CreateEventOptions): EventInterface;
@@ -2,77 +2,70 @@
2
2
  local TS = _G[script]
3
3
  local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
4
4
  local createRemoteInstance = TS.import(script, script.Parent, "createRemoteInstance").createRemoteInstance
5
- local createMiddlewareProcessor = TS.import(script, script.Parent.Parent, "middleware", "createMiddlewareProcessor").createMiddlewareProcessor
6
- --* A sender whose type has blob slots always sends a table; one without never does.
7
- local NO_BLOBS = {}
5
+ local _processor = TS.import(script, script.Parent.Parent, "middleware", "processor")
6
+ local createProcessor = _processor.createProcessor
7
+ local createReceiver = _processor.createReceiver
8
+ local decode = _processor.decode
9
+ local deliverTo = _processor.deliverTo
10
+ local _signal = TS.import(script, script.Parent.Parent, "util", "signal")
11
+ local createSignal = _signal.createSignal
12
+ local spawn = _signal.spawn
13
+ local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
8
14
  --[[
9
15
  *
10
16
  * The argument list a remote delivered: `args` as they are without a decoder, otherwise the buffer
11
17
  * and blob list unpacked. `undefined` when the payload was malformed, after reporting it through
12
18
  * `onMalformed`. Decoding runs under `pcall`: a hostile buffer raises instead of yielding garbage.
19
+ *
20
+ * Either way the list is trimmed after its last value, so that no hop after it loses the values
21
+ * that follow a nil (see `trimArguments`). A decoded list keeps one slot per declared parameter,
22
+ * so an absent trailing optional would otherwise end it in nil.
23
+ *
24
+ * An event's own messages are decoded in the receive pipeline (`processor.luau`); this is for a
25
+ * function's requests and responses, which carry a plain request id ahead of the payload.
13
26
 
14
27
  ]]
15
28
  local function decodeArguments(decoder, player, args, onMalformed)
16
29
  if not decoder then
17
- return args
30
+ return trimArguments(args)
18
31
  end
19
- local _binding = args
20
- local payload = _binding[1]
21
- local blobs = _binding[2]
22
- if not (type(payload) == "buffer") or (blobs ~= nil and not (type(blobs) == "table")) then
23
- local _result = onMalformed
24
- if _result ~= nil then
25
- _result(player, "payload is not a buffer with an optional blob list")
26
- end
27
- return nil
28
- end
29
- local ok, result = pcall(decoder, payload, (blobs or NO_BLOBS))
32
+ local ok, result = decode(decoder, args[1], args[2])
30
33
  if not ok then
31
34
  local _result = onMalformed
32
35
  if _result ~= nil then
33
- _result(player, tostring(result))
36
+ _result(player, result)
34
37
  end
35
38
  return nil
36
39
  end
37
- return result
40
+ return trimArguments(result)
38
41
  end
39
42
  local function createEvent(options)
40
43
  local remote = createRemoteInstance(if options.reliability == "unreliable" then "UnreliableRemoteEvent" else "RemoteEvent", options.namespace, options.debugName, options.id)
41
- local bindable
42
- local invoke = createMiddlewareProcessor(options.incomingMiddleware, options.networkInfo, function(player, ...)
43
- local args = { ... }
44
- if RunService:IsServer() then
45
- bindable:Fire(player, unpack(args))
46
- else
47
- bindable:Fire(unpack(args))
44
+ -- Which half this event is, fixed when it is made: the server's handlers are given the sender.
45
+ local isServer = RunService:IsServer()
46
+ -- Passes the arguments by reference. A BindableEvent would copy them, turning a decoded
47
+ -- `Map<Instance, ...>` into one keyed by strings and raising on a `Set<boolean>`.
48
+ local signal = createSignal()
49
+ -- Guards, then middleware, then the signal: plain calls in the thread that received the message.
50
+ local process = createProcessor(options.incomingMiddleware, options.networkInfo, deliverTo(signal, isServer), options.incomingGuards, nil, nil)
51
+ -- Nothing to deliver to until something connects (a `predict` may come first), so the remote is
52
+ -- only listened to from the first connection on.
53
+ local listening = false
54
+ local listen = function()
55
+ if listening then
56
+ return nil
48
57
  end
49
- end)
50
- local receive = function(player, args)
51
- local decoded = decodeArguments(options.incomingDecoder, player, args, options.onMalformed)
52
- if decoded then
53
- invoke(player, unpack(decoded))
54
- end
55
- end
56
- local createConnection = function(callback)
57
- if bindable then
58
- return bindable.Event:Connect(callback)
59
- end
60
- bindable = Instance.new("BindableEvent")
58
+ listening = true
61
59
  -- We defer to allow any other immediate connections to take place before unloading Roblox's queue.
62
60
  task.defer(function()
63
- if RunService:IsServer() then
64
- remote.OnServerEvent:Connect(function(player, ...)
65
- local args = { ... }
66
- return receive(player, args)
67
- end)
61
+ -- Runs in the thread the engine gives the handler, one per message.
62
+ local receive = createReceiver(options.incomingDecoder, options.onMalformed, process, isServer)
63
+ if isServer then
64
+ remote.OnServerEvent:Connect(receive)
68
65
  else
69
- remote.OnClientEvent:Connect(function(...)
70
- local args = { ... }
71
- return receive(nil, args)
72
- end)
66
+ remote.OnClientEvent:Connect(receive)
73
67
  end
74
68
  end)
75
- return bindable.Event:Connect(callback)
76
69
  end
77
70
  return {
78
71
  fireEither = function(self, player, ...)
@@ -98,14 +91,19 @@ local function createEvent(options)
98
91
  connectServer = function(self, callback)
99
92
  local _arg0 = RunService:IsServer()
100
93
  assert(_arg0)
101
- return createConnection(callback)
94
+ listen()
95
+ return signal:Connect(callback)
102
96
  end,
103
97
  connectClient = function(self, callback)
104
98
  local _arg0 = RunService:IsClient()
105
99
  assert(_arg0)
106
- return createConnection(callback)
100
+ listen()
101
+ return signal:Connect(callback)
102
+ end,
103
+ predict = function(self, player, ...)
104
+ local args = { ... }
105
+ spawn(process, player, unpack(args))
107
106
  end,
108
- invoke = invoke,
109
107
  }
110
108
  end
111
109
  return {
@@ -1,9 +1,13 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
2
4
  local function createClientMethod(receiver, sender)
5
+ -- A method that takes an argument list trims it before spreading it, or an explicit trailing
6
+ -- `undefined` would lose the arguments after a gap (see `trimArguments`).
3
7
  local method = {
4
8
  fire = function(self, ...)
5
9
  local args = { ... }
6
- sender:fireServer(unpack(args))
10
+ sender:fireServer(unpack(trimArguments(args)))
7
11
  end,
8
12
  _fire = function(self, ...)
9
13
  local packed = { ... }
@@ -14,13 +18,13 @@ local function createClientMethod(receiver, sender)
14
18
  end,
15
19
  predict = function(self, ...)
16
20
  local args = { ... }
17
- return receiver.invoke(nil, unpack(args))
21
+ receiver:predict(nil, unpack(trimArguments(args)))
18
22
  end,
19
23
  }
20
24
  setmetatable(method, {
21
25
  __call = function(method, ...)
22
26
  local args = { ... }
23
- method:fire(unpack(args))
27
+ method:fire(unpack((trimArguments(args))))
24
28
  end,
25
29
  })
26
30
  return method
@@ -1,6 +1,6 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
- local createGuardMiddleware = TS.import(script, script.Parent.Parent, "middleware", "createGuardMiddleware").createGuardMiddleware
3
+ local createGuards = TS.import(script, script.Parent.Parent, "middleware", "createGuards").createGuards
4
4
  local createEvent = TS.import(script, script.Parent.Parent, "event", "createEvent").createEvent
5
5
  local getNamespaceConfig = TS.import(script, script.Parent.Parent, "util", "getNamespaceConfig").getNamespaceConfig
6
6
  local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
@@ -44,11 +44,13 @@ local function createGenericHandler(globalName, namespaceName, metadata, config,
44
44
  name = effectiveName,
45
45
  globalName = globalName,
46
46
  }
47
+ -- The generated guards run ahead of all user middleware, so middleware never sees a payload that
48
+ -- failed them.
49
+ local incomingGuards
47
50
  if not config.disableIncomingGuards and isIncoming then
48
51
  local guards = metadata.incoming[name]
49
52
  assert(guards)
50
- local _arg0 = createGuardMiddleware(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
51
- table.insert(incomingMiddleware, 1, _arg0)
53
+ incomingGuards = createGuards(name, guards[1], guards[2], networkInfo, config.warnOnInvalidGuards, signals)
52
54
  end
53
55
  -- A malformed serialized payload is reported like a failed guard, with no argument index.
54
56
  local onMalformed = function(player, message)
@@ -70,6 +72,7 @@ local function createGenericHandler(globalName, namespaceName, metadata, config,
70
72
  debugName = name,
71
73
  networkInfo = networkInfo,
72
74
  incomingMiddleware = incomingMiddleware,
75
+ incomingGuards = incomingGuards,
73
76
  }
74
77
  local _left = "incomingDecoder"
75
78
  local _result
@@ -5,6 +5,7 @@ local createClientMethod = TS.import(script, script.Parent, "createClientMethod"
5
5
  local createServerMethod = TS.import(script, script.Parent, "createServerMethod").createServerMethod
6
6
  local createSignalContainer = TS.import(script, script.Parent.Parent, "util", "createSignalContainer").createSignalContainer
7
7
  local createGenericHandler = TS.import(script, script.Parent, "createGenericHandler").createGenericHandler
8
+ local createOnce = TS.import(script, script.Parent.Parent, "util", "createOnce").createOnce
8
9
  local function getDefaultConfiguration(config)
9
10
  local _object = {}
10
11
  local _left = "middleware"
@@ -30,7 +31,9 @@ end
30
31
  local function createNetworkingEvent(globalName)
31
32
  local signals = createSignalContainer()
32
33
  local server
33
- local client
34
+ -- Built once: building it waits for the server's remotes, and a thread that asks meanwhile gets the
35
+ -- same handler rather than a second one wired to the same remotes.
36
+ local client = createOnce()
34
37
  return {
35
38
  createServer = function(self, config, meta)
36
39
  if RunService:IsRunning() and not RunService:IsServer() then
@@ -45,10 +48,9 @@ local function createNetworkingEvent(globalName)
45
48
  if RunService:IsRunning() and not RunService:IsClient() then
46
49
  return nil
47
50
  end
48
- if client == nil then
49
- client = createGenericHandler(globalName, nil, meta, getDefaultConfiguration(config), signals, createClientMethod)
50
- end
51
- return client
51
+ return client(function()
52
+ return createGenericHandler(globalName, nil, meta, getDefaultConfiguration(config), signals, createClientMethod)
53
+ end)
52
54
  end,
53
55
  registerHandler = function(self, key, callback)
54
56
  return signals:connect(key, callback)
@@ -8,6 +8,6 @@ export declare function createServerMethod(receiver: EventInterface, sender: Eve
8
8
  except: (players: Player | Player[], ...args: unknown[]) => void;
9
9
  broadcast: (...args: unknown[]) => void;
10
10
  readonly _flamework_receive?: unknown[] | undefined;
11
- connect: (cb: (player: Player, ...args: unknown[]) => void) => RBXScriptConnection;
11
+ connect: (cb: (player: Player, ...args: unknown[]) => void) => import("../util/signal").SignalConnection;
12
12
  predict: (player: Player, ...args: unknown[]) => void;
13
13
  };
@@ -1,10 +1,14 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local Players = TS.import(script, TS.getModule(script, "@rbxts", "services")).Players
4
+ local trimArguments = TS.import(script, script.Parent.Parent, "util", "trimArguments").trimArguments
4
5
  local function createServerMethod(receiver, sender)
6
+ -- A method that takes an argument list trims it before spreading it, or an explicit trailing
7
+ -- `undefined` would lose the arguments after a gap (see `trimArguments`).
5
8
  local method = {
6
9
  fire = function(self, players, ...)
7
10
  local args = { ... }
11
+ args = trimArguments(args)
8
12
  local _players = players
9
13
  if typeof(_players) == "Instance" then
10
14
  sender:fireClient(players, unpack(args))
@@ -16,10 +20,11 @@ local function createServerMethod(receiver, sender)
16
20
  end,
17
21
  broadcast = function(self, ...)
18
22
  local args = { ... }
19
- sender:fireAllClients(unpack(args))
23
+ sender:fireAllClients(unpack(trimArguments(args)))
20
24
  end,
21
25
  except = function(self, players, ...)
22
26
  local args = { ... }
27
+ args = trimArguments(args)
23
28
  local _players = players
24
29
  if typeof(_players) == "Instance" then
25
30
  players = { players }
@@ -47,13 +52,13 @@ local function createServerMethod(receiver, sender)
47
52
  end,
48
53
  predict = function(self, player, ...)
49
54
  local args = { ... }
50
- receiver.invoke(player, unpack(args))
55
+ receiver:predict(player, unpack(trimArguments(args)))
51
56
  end,
52
57
  }
53
58
  setmetatable(method, {
54
59
  __call = function(method, player, ...)
55
60
  local args = { ... }
56
- method:fire(player, unpack(args))
61
+ method:fire(player, unpack(trimArguments(args)))
57
62
  end,
58
63
  })
59
64
  return method
@@ -10,6 +10,7 @@ import {
10
10
  } from "../types";
11
11
  import { EventNetworkingEvents } from "../handlers";
12
12
  import { EventMiddleware } from "../middleware/types";
13
+ import { SignalConnection } from "../util/signal";
13
14
  import { Modding } from "@flamework-experimental/core";
14
15
 
15
16
  /**
@@ -56,7 +57,7 @@ export interface RawServerReceiver<I extends unknown[]> {
56
57
  * Connect to this networking event.
57
58
  * @param callback The callback that will be fired
58
59
  */
59
- connect(cb: (player: Player, ...args: I) => void): RBXScriptConnection;
60
+ connect(cb: (player: Player, ...args: I) => void): SignalConnection;
60
61
 
61
62
  /**
62
63
  * Fires a server event using player as the sender.
@@ -91,7 +92,7 @@ export interface RawClientReceiver<I extends unknown[]> {
91
92
  * Connect to this networking event.
92
93
  * @param callback The callback that will be fired
93
94
  */
94
- connect(cb: (...args: I) => void): RBXScriptConnection;
95
+ connect(cb: (...args: I) => void): SignalConnection;
95
96
 
96
97
  /**
97
98
  * Fires a client event.
@@ -174,7 +175,7 @@ export interface GlobalEvent<S, C> {
174
175
  registerHandler<K extends keyof EventNetworkingEvents>(
175
176
  key: K,
176
177
  callback: EventNetworkingEvents[K],
177
- ): RBXScriptConnection;
178
+ ): SignalConnection;
178
179
  }
179
180
 
180
181
  export type EventNamespaces<T> = ExcludeMembers<T, Callback>;
@@ -2,6 +2,7 @@ import { Serialization } from "@flamework-experimental/core";
2
2
  import { NetworkInfo } from "../types";
3
3
  import { NetworkingFunctionError } from "./errors";
4
4
  import { MiddlewareFactory } from "../middleware/types";
5
+ import { Guards } from "../middleware/processor";
5
6
  export interface CreateFunctionReceiverOptions {
6
7
  /**
7
8
  * The namespace this event should be created in.
@@ -22,18 +23,18 @@ export interface CreateFunctionReceiverOptions {
22
23
  */
23
24
  networkInfo: NetworkInfo;
24
25
  /**
25
- * This function will be called when we receive a response, and can be used to resolve or reject values.
26
+ * The middleware a request goes through before it reaches the callback.
26
27
  */
27
28
  incomingMiddleware?: MiddlewareFactory<any[], any>[];
28
29
  /**
29
- * Unpacks the request's argument list. Absent when the project does not enable serialization.
30
+ * The generated argument checks, run on a request before any middleware. A request that fails
31
+ * them is rejected with `BadRequest`.
30
32
  */
31
- argsDecoder?: Serialization.Decoder;
33
+ incomingGuards?: Guards;
32
34
  /**
33
- * Unpacks the results a packed callback returns, so that `predict` resolves with the value itself.
34
- * Absent when the project does not enable serialization.
35
+ * Unpacks the request's argument list. Absent when the project does not enable serialization.
35
36
  */
36
- resultDecoder?: Serialization.Decoder;
37
+ argsDecoder?: Serialization.Decoder;
37
38
  /**
38
39
  * Called when a request cannot be decoded; the caller receives `BadRequest`.
39
40
  */
@@ -44,9 +45,9 @@ export interface RequestInfo {
44
45
  requests: Map<number, (value: unknown, rejection?: NetworkingFunctionError) => void>;
45
46
  }
46
47
  export interface FunctionReceiverInterface {
47
- /** `packed`: the callback returns successful results as `[payload, blobs?]`, packed by the transformer. */
48
- setServerCallback(callback: (player: Player, ...args: unknown[]) => unknown, packed?: boolean): void;
49
- setClientCallback(callback: (...args: unknown[]) => unknown, packed?: boolean): void;
48
+ /** `pack`: turns a successful result into `[payload, blobs?]`, generated by the transformer. */
49
+ setServerCallback(callback: (player: Player, ...args: unknown[]) => unknown, pack?: (value: unknown) => unknown): void;
50
+ setClientCallback(callback: (...args: unknown[]) => unknown, pack?: (value: unknown) => unknown): void;
50
51
  invoke(player: Player | undefined, ...args: unknown[]): Promise<unknown>;
51
52
  }
52
53
  export declare function createFunctionReceiver(options: CreateFunctionReceiverOptions): FunctionReceiverInterface;