Skip to content

Commit

Permalink
feat: use tuples for async return types (#5)
Browse files Browse the repository at this point in the history
* feat: use tuples for async return types

* chore: add todo to update type packs

remove `nil` from type packs when JohnnyMorganz/StyLua#730 is released in the next StyLua version
  • Loading branch information
littensy authored Jul 8, 2023
1 parent 8d55b06 commit deef70b
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 74 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ In Luau, for full type-checking in your editor, you will need to define a separa

- `ServerToClient<Args...>`: A remote event that is fired by the server and processed by the client.

- `ClientToServerAsync<Result, (Args...)>`: A remote function that is invoked by the client and processed by the server.
- `ServerAsync<Args..., Returns...>`: A remote function that is invoked by the client and processed by the server.

- ~~`ServerToClientAsync<Result, (Args...)>`~~: A remote function that is invoked by the server and processed by the client. Not recommended, as requesting values from the client is unsafe.
- ~~`ClientAsync<Args..., Returns...>`~~: A remote function that is invoked by the server and processed by the client. Not recommended, as requesting values from the client is unsafe.

```lua
type Remotes = {
client: Remo.ServerToClient<number>,
server: Remo.ClientToServer<number>,
serverAsync: Remo.ClientToServerAsync<string, (number)>,
serverAsync: Remo.ServerAsync<(number), (string)>,
namespace: {
client: Remo.ServerToClient<number>,
server: Remo.ClientToServer<number>,
Expand Down
33 changes: 16 additions & 17 deletions src/Promise.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
--!nonstrict
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local include = script:FindFirstAncestor("rbxts_include")
Expand Down Expand Up @@ -54,22 +53,22 @@ export type _Promise = {
now: (self: _Promise, rejectionValue: unknown) -> _Promise,
}

export type Promise<T = any> = {
timeout: (self: Promise<T>, seconds: number, rejectionValue: unknown) -> Promise<T>,
getStatus: (self: Promise<T>) -> PromiseStatus,
andThen: (self: Promise<T>, successHandler: (T) -> (), failureHandler: ((...any) -> ())?) -> _Promise,
catch: (self: Promise<T>, failureHandler: (any) -> ()) -> Promise<T>,
tap: (self: Promise<T>, successHandler: (T) -> ()) -> Promise<T>,
andThenCall: <U...>(self: Promise<T>, successHandler: (U...) -> (), U...) -> Promise<T>,
andThenReturn: (self: Promise<T>, value: any) -> _Promise,
cancel: (self: Promise<T>) -> (),
finally: (self: Promise<T>, callback: (status: PromiseStatus) -> ()) -> _Promise,
finallyCall: <U...>(self: Promise<T>, callback: (U...) -> (), U...) -> _Promise,
finallyReturn: (self: Promise<T>, value: any) -> _Promise,
awaitStatus: (self: Promise<T>) -> (PromiseStatus, T),
await: (self: Promise<T>) -> (boolean, T | unknown),
expect: (self: Promise<T>) -> T,
now: (self: Promise<T>, rejectionValue: unknown) -> Promise<T>,
export type Promise<T... = ...any> = {
timeout: (self: Promise<T...>, seconds: number, rejectionValue: unknown) -> Promise<T...>,
getStatus: (self: Promise<T...>) -> PromiseStatus,
andThen: (self: Promise<T...>, successHandler: (T...) -> (), failureHandler: ((...any) -> ())?) -> _Promise,
catch: (self: Promise<T...>, failureHandler: (any) -> ()) -> Promise<T...>,
tap: (self: Promise<T...>, successHandler: (T...) -> ()) -> Promise<T...>,
andThenCall: <U...>(self: Promise<T...>, successHandler: (U...) -> (), U...) -> Promise<T...>,
andThenReturn: (self: Promise<T...>, value: any) -> _Promise,
cancel: (self: Promise<T...>) -> (),
finally: (self: Promise<T...>, callback: (status: PromiseStatus) -> ()) -> _Promise,
finallyCall: <U...>(self: Promise<T...>, callback: (U...) -> (), U...) -> _Promise,
finallyReturn: (self: Promise<T...>, value: any) -> _Promise,
awaitStatus: (self: Promise<T...>) -> (PromiseStatus, T...),
await: (self: Promise<T...>) -> (boolean, T...),
expect: (self: Promise<T...>) -> T...,
now: (self: Promise<T...>, rejectionValue: unknown) -> Promise<T...>,
}

if include and include:FindFirstChild("Promise") then
Expand Down
4 changes: 2 additions & 2 deletions src/client/createAsyncRemote.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ local function createAsyncRemote(name: string, builder: types.RemoteBuilder): ty
end
end

local asyncRemoteNotCallable: types.AsyncRemoteNotCallable = {
local api: types.AsyncRemoteApi = {
name = name,
type = "function" :: "function",
onRequest = onRequest,
request = request,
destroy = destroy,
}

local asyncRemote = setmetatable(asyncRemoteNotCallable, {
local asyncRemote = setmetatable(api, {
__call = request,
}) :: types.AsyncRemote

Expand Down
5 changes: 3 additions & 2 deletions src/createRemotes.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ return function()
local builder = require(script.Parent.builder)
local mockRemotes = require(script.Parent.utils.mockRemotes)

-- TODO: remove 'nil' from type packs
local remotes: types.Remotes<{
event: types.ClientToServer<string, number>,
callback: types.ClientToServerAsync<string, (string, number)>,
callback: types.ServerAsync<(string, number), (string, nil)>,
namespace: {
event: types.ClientToServer<string, number>,
callback: types.ClientToServerAsync<string, (string, number)>,
callback: types.ServerAsync<(string, number), (string, nil)>,
},
}>

Expand Down
19 changes: 12 additions & 7 deletions src/init.lua
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
local Promise = require(script.Promise)
local types = require(script.types)
local createRemotes = require(script.createRemotes)
local builder = require(script.builder)
local getSender = require(script.getSender)
local loggerMiddleware = require(script.middleware.loggerMiddleware)
local throttleMiddleware = require(script.middleware.throttleMiddleware)

export type Promise<T> = types.Promise<T>
export type PromiseConstructor = types.PromiseConstructor
export type Validator = types.Validator
export type Promise<T> = Promise.Promise<T>
export type PromiseConstructor = Promise.PromiseConstructor

export type Middleware = types.Middleware
export type MiddlewareContext = types.MiddlewareContext

export type RemoteBuilder = types.RemoteBuilder
export type RemoteBuilderMetadata = types.RemoteBuilderMetadata
Expand All @@ -24,9 +23,15 @@ export type Remote<Args... = ...any> = types.Remote<Args...>
export type ClientToServer<Args... = ...any> = types.ClientToServer<Args...>
export type ServerToClient<Args... = ...any> = types.ServerToClient<Args...>

export type AsyncRemote<Returns = any, Args... = ...any> = types.AsyncRemote<Returns, Args...>
export type ClientToServerAsync<Returns = any, Args... = ...any> = types.ClientToServerAsync<Returns, Args...>
export type ServerToClientAsync<Returns = any, Args... = ...any> = types.ServerToClientAsync<Returns, Args...>
export type AsyncRemote<Args... = ...any, Returns... = ...any> = types.AsyncRemote<Args..., Returns...>
export type ServerAsync<Args... = ...any, Returns... = ...any> = types.ServerAsync<Args..., Returns...>
export type ClientAsync<Args... = ...any, Returns... = ...any> = types.ClientAsync<Args..., Returns...>

--- TODO: remove 'nil' from type packs
--- @deprecated 1.2, use `ServerAsync` instead.
export type ClientToServerAsync<Returns = any, Args... = ...any> = types.ServerAsync<Args..., (Returns, nil)>
--- @deprecated 1.2, use `ClientAsync` instead.
export type ServerToClientAsync<Returns = any, Args... = ...any> = types.ClientAsync<Args..., (Returns, nil)>

return {
remote = builder.remote,
Expand Down
4 changes: 2 additions & 2 deletions src/middleware/throttleMiddleware.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ return function()
end

describe("event throttle", function()
local remotes, remote: types.ServerToClient, instance: RemoteEvent
local remotes, remote: types.ClientToServer, instance: RemoteEvent

local function create(options: throttleMiddleware.ThrottleMiddlewareOptions)
remotes = createRemotes({ remote = builder.remote() }, throttleMiddleware(options))
Expand Down Expand Up @@ -144,7 +144,7 @@ return function()
end)

describe("async throttle", function()
local remotes, remote: types.ServerToClientAsync, instance: RemoteFunction
local remotes, remote: types.ServerAsync, instance: RemoteFunction

local function create(options: throttleMiddleware.ThrottleMiddlewareOptions)
remotes = createRemotes({ remote = builder.remote().returns() }, throttleMiddleware(options))
Expand Down
6 changes: 3 additions & 3 deletions src/server/createAsyncRemote.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local function createAsyncRemote(name: string, builder: types.RemoteBuilder): ty
local instance = instances.createRemoteFunction(name)
local connected = true

local function handler(...): any
local function handler(...): ...any
return
end

Expand Down Expand Up @@ -43,15 +43,15 @@ local function createAsyncRemote(name: string, builder: types.RemoteBuilder): ty
end
end

local asyncRemoteNotCallable: types.AsyncRemoteNotCallable = {
local api: types.AsyncRemoteApi = {
name = name,
type = "function" :: "function",
onRequest = onRequest,
request = request,
destroy = destroy,
}

local asyncRemote = setmetatable(asyncRemoteNotCallable, {
local asyncRemote = setmetatable(api, {
__call = request,
}) :: types.AsyncRemote

Expand Down
70 changes: 32 additions & 38 deletions src/types.lua
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
local Promise = require(script.Parent.Promise)

export type Promise<T> = Promise.Promise<T>
type Cleanup = () -> ()

export type PromiseConstructor = Promise.PromiseConstructor
type Promise<T...> = Promise.Promise<T...>

export type Validator = any

export type Cleanup = () -> ()

export type Middleware = (next: (...any) -> ...any, remote: AnyRemote) -> (...any) -> ...any

export type MiddlewareContext = {
player: Player,
}

export type RemoteBuilder = {
type: RemoteType,
metadata: RemoteBuilderMetadata,
Expand Down Expand Up @@ -42,6 +36,14 @@ export type AnyRemote = Remote | AsyncRemote

export type Remote<Args... = ...any> = ClientToServer<Args...> & ServerToClient<Args...>

export type Remotes<Map = RemoteMap> = Map & {
destroy: (self: Remotes<Map>) -> (),
}

export type RemoteMap = {
[string]: AnyRemote | RemoteMap,
}

export type ClientToServer<Args... = ...any> = {
name: string,
type: "event",
Expand All @@ -61,50 +63,42 @@ export type ServerToClient<Args... = ...any> = {
destroy: (self: ServerToClient<Args...>) -> (),
}

export type AsyncRemote<Returns = any, Args... = ...any> =
ClientToServerAsync<Returns, Args...>
& ServerToClientAsync<Returns, Args...>
export type AsyncRemote<Args... = ...any, Returns... = ...any> =
ServerAsync<Args..., Returns...>
& ClientAsync<Args..., Returns...>

export type ServerToClientAsync<Returns = any, Args... = ...any> =
((player: Player, Args...) -> Promise<Returns>)
& ServerToClientAsyncNotCallable<Returns, Args...>
export type ServerAsync<Args... = ...any, Returns... = ...any> =
((Args...) -> Promise<Returns...>)
& ServerAsyncApi<Args..., Returns...>

export type ClientToServerAsync<Returns = any, Args... = ...any> =
((Args...) -> Promise<Returns>)
& ClientToServerAsyncNotCallable<Returns, Args...>
export type ClientAsync<Args... = ...any, Returns... = ...any> =
((player: Player, Args...) -> Promise<Returns...>)
& ClientAsyncApi<Args..., Returns...>

export type AsyncRemoteNotCallable<Returns = any, Args... = ...any> =
ServerToClientAsyncNotCallable<Returns, Args...>
& ClientToServerAsyncNotCallable<Returns, Args...>
export type AsyncRemoteApi<Args... = ...any, Returns... = ...any> =
ServerAsyncApi<Args..., Returns...>
& ClientAsyncApi<Args..., Returns...>

type ServerToClientAsyncNotCallable<Returns = any, Args... = ...any> = {
export type ServerAsyncApi<Args..., Returns...> = {
name: string,
type: "function",
onRequest: (
self: ServerToClientAsyncNotCallable<Returns, Args...>,
callback: (Args...) -> Returns | Promise<Returns>
self: ServerAsyncApi<Args..., Returns...>,
callback: ((player: Player, Args...) -> Returns...) | (player: Player, Args...) -> Promise<Returns...>
) -> (),
request: (self: ServerToClientAsyncNotCallable<Returns, Args...>, player: Player, Args...) -> Promise<Returns>,
destroy: (self: ServerToClientAsyncNotCallable<Returns, Args...>) -> (),
request: (self: ServerAsyncApi<Args..., Returns...>, Args...) -> Promise<Returns...>,
destroy: (self: ServerAsyncApi<Args..., Returns...>) -> (),
}

type ClientToServerAsyncNotCallable<Returns = any, Args... = ...any> = {
export type ClientAsyncApi<Args..., Returns...> = {
name: string,
type: "function",
onRequest: (
self: ClientToServerAsyncNotCallable<Returns, Args...>,
callback: (player: Player, Args...) -> Returns | Promise<Returns>
self: ClientAsyncApi<Args..., Returns...>,
callback: ((Args...) -> Returns...) | (Args...) -> Promise<Returns...>
) -> (),
request: (self: ClientToServerAsyncNotCallable<Returns, Args...>, Args...) -> Promise<Returns>,
destroy: (self: ClientToServerAsyncNotCallable<Returns, Args...>) -> (),
}

export type Remotes<Map = RemoteMap> = Map & {
destroy: (self: Remotes<Map>) -> (),
}

export type RemoteMap = {
[string]: AnyRemote | RemoteMap,
request: (self: ClientAsyncApi<Args..., Returns...>, Args...) -> Promise<Returns...>,
destroy: (self: ClientAsyncApi<Args..., Returns...>) -> (),
}

return nil

0 comments on commit deef70b

Please sign in to comment.