UNPKG

@rbxts/remo

Version:

A simple remote library for Roblox

695 lines (659 loc) 23.9 kB
type Validator<T = unknown> = (value: unknown) => value is T; type InferValidator<T extends Validator> = T extends Validator<infer U> ? U : never; type InferValidators<T extends Validator[]> = { [K in keyof T]: T[K] extends Validator<infer U> ? U : never; }; type Validators<T extends unknown[]> = { [K in keyof T]: Validator<T[K]>; }; type Cleanup = () => void; export = Remo; export as namespace Remo; declare namespace Remo { /** * Declares a two-way remote that is invoked with the arguments passed to the * template. Returns a remote builder that can be used to further configure * the remote. * * To declare a Client or Server remote, pass the Client or Server type as * the first type parameter before the arguments. * * @template Args The arguments that the remote accepts. */ export function remote<Args extends unknown[]>( ...validators: Partial<Validators<Args>> ): RemoteBuilder<(...args: Args) => void>; /** * Declares a remote that is invoked with the arguments passed to the * template. Returns a remote builder that can be used to further configure * the remote. * * You can pass three remote modes: * - `Client` - processed on the client and invoked by the server * - `Server` - processed on the server and invoked by the client * - `TwoWay` - both the client and server have full access to the remote * * @template Mode Whether the remote is processed by the client, the server, * or both. * @template Args The arguments that the remote accepts. */ export function remote<Mode extends RemoteMode, Args extends unknown[] = []>( ...validators: Partial<Validators<Args>> ): RemoteBuilder<(...args: Args) => void, Mode>; /** * Declares a two-way remote with a signature inferred from the validators. * Returns a remote builder that can be used to further configure the remote. * * You may instead pass a type parameter to explicitly define the arguments * for the remote. */ export function remote<T extends Validator[]>(...validators: T): RemoteBuilder<(...args: InferValidators<T>) => void>; /** * Declares a namespace that can contain other remotes and namespaces. */ export function namespace<T extends RemoteSchema>(schema: T): RemoteNamespace<T>; /** * Creates the remotes and namespaces as defined by the schema. Returns an * object containing the remotes and namespaces in the same shape as the * schema. */ export function createRemotes<T extends RemoteSchema>(schema: T, ...middleware: RemoteMiddleware[]): Remotes<T>; /** * Returns the player who invoked the remote given the first argument passed * to the remote. If the first argument is an instance or a table with a * `ClassName` property set to `"Player"`, it will be returned. */ export function getSender(...args: unknown[]): Player | undefined; interface ThrottleMiddlewareOptions { /** * The number of seconds to wait before the remote can be invoked again. * * @default 0.1 */ readonly throttle?: number; /** * Whether to fire the remote one more time after the throttle period has * passed. This only applies to events, and not async remotes. * * @default false */ readonly trailing?: boolean; } /** * Prints detailed messages to the console when the remote is fired or invoked. */ export const loggerMiddleware: RemoteMiddleware; /** * Throttles the remote so that it can only be invoked once every `throttle` * seconds. If you want more control over the throttle behavior, pass an * `options` object instead. * * If applied to an async remote, the remote will try to resolve with the * previous result if it is invoked during the throttle period. */ export function throttleMiddleware(throttle: number): RemoteMiddleware; /** * Throttles the remote so that it can only be invoked once every `throttle` * seconds. If `trailing` is true, the remote will fire one more time after * the throttle period has passed. * * If applied to an async remote, the remote will try to resolve with the * previous result if it is invoked during the throttle period. Otherwise, * the remote will reject with an error. */ export function throttleMiddleware(options?: ThrottleMiddlewareOptions): RemoteMiddleware; } declare namespace Remo { /** * This remote will be processed by the server and is invoked by the client. */ export interface Server { readonly __brand: unique symbol; } /** * This remote will be processed by the client and is invoked by the server. */ export interface Client { readonly __brand: unique symbol; } /** * This remote will be processed by both the client and the server and can be * invoked by either. * * @deprecated As of 1.2, prefer `Server` or `Client` instead. */ export interface TwoWay { readonly __brand: unique symbol; } /** * The remote mode determines whether the remote is processed on the client, * the server, or both. */ export type RemoteMode = Server | Client | TwoWay; /** * A container for the remotes and namespaces as defined by the schema. */ export type Remotes<Schema extends RemoteSchema> = { [K in keyof Schema]: Schema[K] extends RemoteNamespace<infer NestedSchema> ? Remotes<NestedSchema> : Schema[K] extends RemoteBuilder ? InferRemoteFromBuilder<Schema[K]> : never; }; type InferRemoteFromBuilder<T extends RemoteBuilder> = T extends RemoteBuilder<infer Signature, infer Mode> ? Signature extends (...args: infer Args) => infer Returns ? [Returns] extends [void] ? Mode extends Server ? ServerRemote<Args> : Mode extends Client ? ClientRemote<Args> : ServerRemote<Args> | ClientRemote<Args> : Mode extends Server ? ServerAsyncRemote<Args, Returns> : Mode extends Client ? ClientAsyncRemote<Args, Returns> : ServerAsyncRemote<Args, Returns> | ClientAsyncRemote<Args, Returns> : never : never; /** * Declares the structure of the remotes and namespaces that will be created * by `createRemotes`. */ export interface RemoteSchema { [remoteName: string]: RemoteBuilder | RemoteNamespace; } /** * A namespace is a container for other remotes and namespaces. */ export interface RemoteNamespace<Schema extends RemoteSchema = RemoteSchema> { readonly remotes: Schema; } /** * Declares the mode and signature of a remote. The mode determines whether * the remote is processed on the client, the server, or both. The signature * determines the arguments and optional return value of the remote. * * @template Signature The function signature of the remote. */ export interface RemoteBuilder<Signature extends Callback = Callback, Mode extends RemoteMode = TwoWay> { /** * @deprecated This is used internally for nominal typing. */ readonly __signature: Signature; /** * Declares that the remote has a return value, and will create an async * remote when passed to `createRemotes`. */ readonly returns: { /** * Declares that the remote has a return value, and will create an async * remote when passed to `createRemotes`. * * The type of the return value is inferred from the validator. You may * instead pass a type parameter to explicitly declare the return type. */ <T extends Validator>(validator: T): RemoteBuilder< Signature extends (...args: infer Args) => void ? (...args: Args) => InferValidator<T> : never, Mode >; /** * Declares that the remote has a return value, and will create an async * remote when passed to `createRemotes`. * * Accepts a type parameter to explicitly declare the return type. * Validators are optional. * * @template T The type of the return value. */ <T>(validator?: Validator<T>): RemoteBuilder< Signature extends (...args: infer Args) => void ? (...args: Args) => T : never, Mode >; }; /** * Applies middleware to the remote once it is created by `createRemotes`. * * Middleware declared here will be applied before any middleware declared * through a namespace or the `createRemotes` function. */ readonly middleware: (...middleware: RemoteMiddleware[]) => RemoteBuilder<Signature, Mode>; /** * Marks the remote as an unreliable remote. Uses an `UnreliableRemoteEvent` * internally if the remote is an event. * * @see https://devforum.roblox.com/t/introducing-unreliableremoteevents/2724155 */ readonly unreliable: () => RemoteBuilder<Signature, Mode>; } /** * A middleware function that can be applied to modify the behavior of a * remote. Returns a function that is called when the remote is invoked. * * @param next The next middleware function in the chain. * @param remote The remote that is being invoked. * @returns A function that is called when the remote is invoked. */ export type RemoteMiddleware = (next: Callback, remote: AnyRemote) => (...args: unknown[]) => unknown; } declare namespace Remo { /** * The type of a remote. Either `"event"` for a RemoteEvent or `"function"` * for a RemoteFunction. */ export const enum RemoteType { Event = "event", Function = "function", } interface BaseRemote<T extends RemoteType = RemoteType> { /** * The type of the remote. Either `"event"` if this is a remote event or * `"function"` if this is an async remote. */ readonly type: T; /** * The name of the remote, as defined in the schema. */ readonly name: string; /** * Destroys the remote. If called on the client, this will disconnect all * listeners from the remote. If called on the server, this will destroy * the instance. */ destroy(): void; } export type AnyRemote<Args extends unknown[] = unknown[], Returns = unknown> = | ServerRemote<Args> | ClientRemote<Args> | ServerAsyncRemote<Args, Returns> | ClientAsyncRemote<Args, Returns>; /** * A two-way remote event that runs the connected listeners when it is fired. * It can be fired and connected to on both the client and the server. * * @deprecated Use a client or server remote instead. */ export interface Remote<Args extends unknown[] = unknown[]> extends ServerRemote<Args>, ClientRemote<Args> { /** * Fires the remote for the player to process. Calls the player's listeners * connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ fire(player: Player, ...args: Args): void; /** * Fires the remote for the server to process. Calls the listeners * connected to the same remote. * * Arguments are validated on the server before they are processed. * * @client */ fire(...args: Args): void; /** * Connects a server-side listener to the remote. When a player fires * the remote, the listener will be invoked with the arguments passed to * the remote. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the listener. * * @server */ connect(listener: (player: Player, ...args: Args) => void): Cleanup; /** * Connects a client-side listener to the remote. When the server fires * the remote, the listener will be invoked with the arguments passed to * the remote. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the listener. * * @client */ connect(listener: (...args: Args) => void): Cleanup; /** * Returns a Promise that resolves when the remote is fired by a player. * If `predicate` is provided, the Promise will only resolve if the * predicate returns true. * * NOTE: Promises in Roblox-TS do not support tuples. Use the `mapper` * argument to map the tuple to a consumable value. * * @server */ promise<T = Player>( predicate?: (player: Player, ...args: Args) => boolean, mapper?: (player: Player, ...args: Args) => T, ): Promise<T>; /** * Returns a Promise that resolves when the remote is fired by the server. * If `predicate` is provided, the Promise will only resolve if the * predicate returns true. * * NOTE: Promises in Roblox-TS do not support tuples. Use the `mapper` * argument to map the tuple to a consumable value. * * @client */ promise<T = Args[0]>(predicate?: (...args: Args) => boolean, mapper?: (...args: Args) => T): Promise<T>; } /** * A server-side remote event that runs the connected listeners when it is * fired. It is fired by the client and events are processed by the server. */ export interface ServerRemote<Args extends unknown[]> extends BaseRemote<RemoteType.Event> { /** * Fires the remote for the server to process. Calls the listeners * connected to the same remote. * * Arguments are validated on the server before they are processed. * * @client */ (...args: Args): void; /** * Provides an interface to subscribe to or intercept this remote's own * outgoing events. Useful for mocking a client or server's response. */ readonly test: RemoteTester<Args>; /** * Fires the remote for the server to process. Calls the listeners * connected to the same remote. * * Arguments are validated on the server before they are processed. * * @client */ fire(...args: Args): void; /** * Connects a server-side listener to the remote. When a player fires * the remote, the listener will be invoked with the arguments passed to * the remote. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the listener. * * @server */ connect(listener: (player: Player, ...args: Args) => void): Cleanup; /** * Returns a Promise that resolves when the remote is fired by a player. * If `predicate` is provided, the Promise will only resolve if the * predicate returns true. * * NOTE: Promises in Roblox-TS do not support tuples. Use the `mapper` * argument to map the tuple to a consumable value. * * @server */ promise<T = Player>( predicate?: (player: Player, ...args: Args) => boolean, mapper?: (player: Player, ...args: Args) => T, ): Promise<T>; } /** * A client-side remote event that runs the connected listeners when it is * fired. It is fired by the server and events are processed by the client. */ export interface ClientRemote<Args extends unknown[]> extends BaseRemote<RemoteType.Event> { /** * Fires the remote for the player to process. Calls the player's listeners * connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ (player: Player, ...args: Args): void; /** * Provides an interface to subscribe to or intercept this remote's own * outgoing events. Useful for mocking a client or server's response. */ readonly test: RemoteTester<Args>; /** * Fires the remote for the player to process. Calls the player's listeners * connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ fire(player: Player, ...args: Args): void; /** * Fires the remote for the given players to process. Calls the players' * listeners connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ firePlayers(players: readonly Player[], ...args: Args): void; /** * Fires the remote for all players to process. Calls all players' listeners * connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ fireAll(...args: Args): void; /** * Fires the remote for all players except the given player to process. * Calls all players' listeners connected to the same remote. * * Arguments are validated on the client before they are processed. * * @server */ fireAllExcept(player: Player, ...args: Args): void; /** * Connects a client-side listener to the remote. When the server fires * the remote, the listener will be invoked with the arguments passed to * the remote. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the listener. * * @client */ connect(listener: (...args: Args) => void): Cleanup; /** * Returns a Promise that resolves when the remote is fired by the server. * If `predicate` is provided, the Promise will only resolve if the * predicate returns true. * * NOTE: Promises in Roblox-TS do not support tuples. Use the `mapper` * argument to map the tuple to a consumable value. * * @client */ promise<T = Args[0]>(predicate?: (...args: Args) => boolean, mapper?: (...args: Args) => T): Promise<T>; } /** * A two-way remote function that runs the handler when it is invoked. It can * be invoked and processed on both the client and the server. * * @deprecated Use a client or server async remote instead. */ export interface AsyncRemote<Args extends unknown[] = unknown[], Returns = unknown> extends ServerAsyncRemote<Args, Returns>, ClientAsyncRemote<Args, Returns> { /** * Sends a request for a player to process. Returns a Promise that resolves * with the value returned by the handler. * * Arguments are validated on the client before they are processed. * * @server * @deprecated Requesting values from players is unsafe. */ request(player: Player, ...args: Args): Promise<Returns>; /** * Sends a request for the server to process. Returns a Promise that resolves * with the return value of the server handler. * * Arguments are validated on the server before they are processed. * * @client */ request(...args: Args): Promise<Returns>; /** * Binds a server-side handler to the remote. When a player makes a request, * the handler will be invoked with the arguments passed, and the return * value will be sent back to the player. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the handler. Otherwise, the * request will be rejected. * * @server */ onRequest(handler: (player: Player, ...args: Args) => Promise<Returns> | Returns): void; /** * Binds a client-side handler to the remote. When the server makes a * request, the handler will be invoked with the arguments passed, and the * return value will be sent back to the server. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the handler. Otherwise, the * request will be rejected. * * @client * @deprecated Requesting values from players is unsafe. */ onRequest(handler: (...args: Args) => Promise<Returns> | Returns): void; } /** * A server-side remote function that runs the handler when it is invoked. It * is invoked by the client and requests are processed by the server. */ export interface ServerAsyncRemote<Args extends unknown[], Returns> extends BaseRemote<RemoteType.Function> { /** * Sends a request for the server to process. Returns a Promise that resolves * with the return value of the server handler. * * Arguments are validated on the server before they are processed. * * @client */ (...args: Args): Promise<Returns>; /** * Provides an interface to mock a response to a remote request. Useful for * intercepting a request to the server or client. */ readonly test: AsyncRemoteTester<Args, Returns>; /** * Sends a request for the server to process. Returns a Promise that resolves * with the return value of the server handler. * * Arguments are validated on the server before they are processed. * * @client */ request(...args: Args): Promise<Returns>; /** * Binds a server-side handler to the remote. When a player makes a request, * the handler will be invoked with the arguments passed, and the return * value will be sent back to the player. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the handler. Otherwise, the * request will be rejected. * * @server */ onRequest(handler: (player: Player, ...args: Args) => Promise<Returns> | Returns): void; } /** * A client-side remote function that runs the handler when it is invoked. It * is invoked by the server and requests are processed by the client. */ export interface ClientAsyncRemote<Args extends unknown[], Returns> extends BaseRemote<RemoteType.Function> { /** * Sends a request for a player to process. Returns a Promise that resolves * with the value returned by the handler. * * Arguments are validated on the client before they are processed. * * @server * @deprecated Requesting values from players is unsafe. */ (player: Player, ...args: Args): Promise<Returns>; /** * Provides an interface to mock a response to a remote request. Useful for * intercepting a request to the server or client. */ readonly test: AsyncRemoteTester<Args, Returns>; /** * Sends a request for a player to process. Returns a Promise that resolves * with the value returned by the handler. * * Arguments are validated on the client before they are processed. * * @server * @deprecated Requesting values from players is unsafe. */ request(player: Player, ...args: Args): Promise<Returns>; /** * Binds a client-side handler to the remote. When the server makes a * request, the handler will be invoked with the arguments passed, and the * return value will be sent back to the server. * * Arguments passed to the remote must first pass the validators defined * in the schema before they are passed to the handler. Otherwise, the * request will be rejected. * * @client * @deprecated Requesting values from players is unsafe. */ onRequest(handler: (...args: Args) => Promise<Returns> | Returns): void; } /** * Provides an interface to subscribe to or intercept this remote's own * outgoing events. Useful for mocking a client or server's response. */ export interface RemoteTester<Args extends unknown[]> { /** * Subscribes to an outgoing event originating from this side of the client/ * server boundary. The listener will be invoked with the arguments passed * to `remote.fire`, minus any player argument. * * This is _not_ a replacement for `connect`, and will not intercept events * sent by the other side of the client/server boundary. * * **Note:** Remember to clean up your listeners when you are done testing! */ onFire(listener: (...args: Args) => void): Cleanup; /** * Disconnects all listeners from this test remote. */ disconnectAll(): void; } /** * Provides an interface to mock a response to a remote request. Useful for * intercepting a request to the server or client. */ export interface AsyncRemoteTester<Args extends unknown[], Returns> { /** * Forces `request` to call this handler instead of the handler registered * by the recipient. The handler will be invoked with the arguments passed * to `remote.request`, minus any player argument. * * This is _not_ a replacement for `onRequest`, and will not intercept * requests sent by the other side of the client/server boundary. * * **Note:** Remember to call `disconnectAll` when you are done testing! */ handleRequest(handler: (...args: Args) => Promise<Returns> | Returns): void; /** * Returns whether this remote has a handler registered. */ hasRequestHandler(): boolean; /** * Removes the handler registered by `handleRequest`. */ disconnectAll(): void; } }