feat(rpc): rework shared-types + include as mandatory dep

This commit is contained in:
2026-09-28 02:05:40 +01:00
parent 3e3548a9cc
commit 8029f24e9d
9 changed files with 235 additions and 222 deletions
+3 -5
View File
@@ -48,18 +48,16 @@
"prepublishOnly": "bun run build",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@entityseven/fivem-rpc-shared-types": "workspace:^"
},
"devDependencies": {
"@entityseven/fivem-rpc-shared-types": "workspace:*",
"tsdown": "^0.23.0"
},
"peerDependencies": {
"@entityseven/fivem-rpc-shared-types": "^1.0.0",
"typescript": ">=5"
},
"peerDependenciesMeta": {
"@entityseven/fivem-rpc-shared-types": {
"optional": true
},
"typescript": {
"optional": true
}
+43 -56
View File
@@ -17,6 +17,13 @@ import {
type RPCStateRaw,
type RPCStateWeb,
} from '../utils/types'
import type {
RPCCommandName,
RPCEventArgs,
RPCEventName,
RPCEventResult,
RPCListener,
} from '../utils/typing'
import { RPCInstanceBase } from './base'
export class RPCInstanceClient extends RPCInstanceBase {
@@ -116,63 +123,53 @@ export class RPCInstanceClient extends RPCInstanceBase {
// ===== SERVER =====
public onServer<
EventName extends keyof s.RPCEvents_ServerClient,
CallbackArguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ServerClient[EventName]>,
>(
public onServer<EventName extends RPCEventName<s.RPCEvents_ServerClient>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_ServerClient, EventName>,
): this {
return this.listen(this._emitterServer, 'onServer', eventName, cb)
}
public offServer<EventName extends keyof s.RPCEvents_ServerClient>(
public offServer<EventName extends RPCEventName<s.RPCEvents_ServerClient>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterServer, 'offServer', eventName)
}
public async emitServer<
EventName extends keyof s.RPCEvents_ClientServer,
Arguments extends Parameters<s.RPCEvents_ClientServer[EventName]>,
Response extends ReturnType<s.RPCEvents_ClientServer[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
EventName extends RPCEventName<s.RPCEvents_ClientServer>,
>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_ClientServer, EventName>
): Promise<RPCEventResult<s.RPCEvents_ClientServer, EventName>> {
const payload = this.request(eventName, 'server', args, null)
emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload))
return this._pending.wait<Awaited<Response>>(payload)
return this._pending.wait(payload)
}
// ===== WEBVIEW =====
public onWebview<
EventName extends keyof s.RPCEvents_WebviewClient,
CallbackArguments extends Parameters<s.RPCEvents_WebviewClient[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_WebviewClient[EventName]>,
>(
public onWebview<EventName extends RPCEventName<s.RPCEvents_WebviewClient>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_WebviewClient, EventName>,
): this {
return this.listen(this._emitterWeb, 'onWebview', eventName, cb)
}
public offWebview<EventName extends keyof s.RPCEvents_WebviewClient>(
public offWebview<EventName extends RPCEventName<s.RPCEvents_WebviewClient>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterWeb, 'offWebview', eventName)
}
public async emitWebview<
EventName extends keyof s.RPCEvents_ClientWebview,
Arguments extends Parameters<s.RPCEvents_ClientWebview[EventName]>,
Response extends ReturnType<s.RPCEvents_ClientWebview[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
EventName extends RPCEventName<s.RPCEvents_ClientWebview>,
>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_ClientWebview, EventName>
): Promise<RPCEventResult<s.RPCEvents_ClientWebview, EventName>> {
const payload = this.request(eventName, 'webview', args, null)
this._sendWebMessage({
@@ -180,46 +177,36 @@ export class RPCInstanceClient extends RPCInstanceBase {
data: payload,
})
return this._pending.wait<Awaited<Response>>(payload)
return this._pending.wait(payload)
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Client,
CallbackArguments extends Parameters<s.RPCEvents_Client[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Client[EventName]>,
>(
public onSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_Client, EventName>,
): this {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
public offSelf<EventName extends keyof s.RPCEvents_Client>(
public offSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Client,
Arguments extends Parameters<s.RPCEvents_Client[EventName]>,
Response extends ReturnType<s.RPCEvents_Client[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
return this.emitLocal<Awaited<Response>>(eventName, args)
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Client, EventName>
): Promise<RPCEventResult<s.RPCEvents_Client, EventName>> {
return this.emitLocal(eventName, args)
}
// ===== OTHER =====
public onCommand<
CommandName extends s.RPCCommands_Client,
CallbackArguments extends unknown[],
>(
public onCommand<CommandName extends RPCCommandName<s.RPCCommands_Client>>(
command: CommandName,
cb: (player: number, args: CallbackArguments, commandRaw: string) => void,
cb: (player: number, args: string[], rawCommand: string) => void,
): this {
this.log(`onCommand ${command}`)
@@ -228,10 +215,10 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this
}
public onNativeEvent<
EventName extends keyof RPCNativeClientEvents,
CallbackArguments extends Parameters<RPCNativeClientEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
public onNativeEvent<EventName extends keyof RPCNativeClientEvents>(
eventName: EventName,
cb: (...args: Parameters<RPCNativeClientEvents[EventName]>) => void,
): this {
if (!NATIVE_CLIENT_EVENTS.includes(eventName)) {
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
@@ -248,10 +235,10 @@ export class RPCInstanceClient extends RPCInstanceBase {
public onNativeNetworkEvent<
EventName extends keyof RPCNativeClientNetworkEvents,
CallbackArguments extends Parameters<
RPCNativeClientNetworkEvents[EventName]
>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
>(
eventName: EventName,
cb: (...args: Parameters<RPCNativeClientNetworkEvents[EventName]>) => void,
): this {
if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) {
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
+40 -59
View File
@@ -11,6 +11,13 @@ import {
type RPCNativeServerEvents,
type RPCStateRaw,
} from '../utils/types'
import type {
RPCCommandName,
RPCEventArgs,
RPCEventName,
RPCEventResult,
RPCListener,
} from '../utils/typing'
import { RPCInstanceBase } from './base'
export class RPCInstanceServer extends RPCInstanceBase {
@@ -68,46 +75,39 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== CLIENT =====
public onClient<
EventName extends keyof s.RPCEvents_ClientServer,
CallbackArguments extends Parameters<s.RPCEvents_ClientServer[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ClientServer[EventName]>,
>(
public onClient<EventName extends RPCEventName<s.RPCEvents_ClientServer>>(
eventName: EventName,
cb: (
player: number,
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_ClientServer, EventName, [player: number]>,
): this {
return this.listen(this._emitterClient, 'onClient', eventName, cb)
}
public offClient<EventName extends keyof s.RPCEvents_ClientServer>(
public offClient<EventName extends RPCEventName<s.RPCEvents_ClientServer>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterClient, 'offClient', eventName)
}
public async emitClient<
EventName extends keyof s.RPCEvents_ServerClient,
Arguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
Response extends ReturnType<s.RPCEvents_ServerClient[EventName]>,
EventName extends RPCEventName<s.RPCEvents_ServerClient>,
>(
player: number,
eventName: EventName,
...args: Arguments
): Promise<Awaited<Response>> {
...args: RPCEventArgs<s.RPCEvents_ServerClient, EventName>
): Promise<RPCEventResult<s.RPCEvents_ServerClient, EventName>> {
const payload = this.request(eventName, 'client', args, player)
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
return this._pending.wait<Awaited<Response>>(payload, player)
return this._pending.wait(payload, player)
}
public async emitClientEveryone<
EventName extends keyof s.RPCEvents_ServerClient,
Arguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<void> {
EventName extends RPCEventName<s.RPCEvents_ServerClient>,
>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_ServerClient, EventName>
): Promise<void> {
const payload = this.request(eventName, 'client', args, -1, 'broadcast')
emitNet(RPCEvents.LISTENER_SERVER, -1, stringify(payload))
@@ -115,79 +115,60 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== WEBVIEW =====
public onWebview<
EventName extends keyof s.RPCEvents_WebviewServer,
CallbackArguments extends Parameters<s.RPCEvents_WebviewServer[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_WebviewServer[EventName]>,
>(
public onWebview<EventName extends RPCEventName<s.RPCEvents_WebviewServer>>(
eventName: EventName,
cb: (
player: number,
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_WebviewServer, EventName, [player: number]>,
): this {
return this.listen(this._emitterWeb, 'onWebview', eventName, cb)
}
public offWebview<EventName extends keyof s.RPCEvents_WebviewServer>(
public offWebview<EventName extends RPCEventName<s.RPCEvents_WebviewServer>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterWeb, 'offWebview', eventName)
}
public async emitWebview<
EventName extends keyof s.RPCEvents_ServerWebview,
Arguments extends Parameters<s.RPCEvents_ServerWebview[EventName]>,
Response extends ReturnType<s.RPCEvents_ServerWebview[EventName]>,
EventName extends RPCEventName<s.RPCEvents_ServerWebview>,
>(
player: number,
eventName: EventName,
...args: Arguments
): Promise<Awaited<Response>> {
...args: RPCEventArgs<s.RPCEvents_ServerWebview, EventName>
): Promise<RPCEventResult<s.RPCEvents_ServerWebview, EventName>> {
const payload = this.request(eventName, 'webview', args, player)
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
return this._pending.wait<Awaited<Response>>(payload, player)
return this._pending.wait(payload, player)
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Server,
CallbackArguments extends Parameters<s.RPCEvents_Server[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Server[EventName]>,
>(
public onSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_Server, EventName, [player: number]>,
): this {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
public offSelf<EventName extends keyof s.RPCEvents_Server>(
public offSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Server,
Arguments extends Parameters<s.RPCEvents_Server[EventName]>,
Response extends ReturnType<s.RPCEvents_Server[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
return this.emitLocal<Awaited<Response>>(eventName, args)
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Server, EventName>
): Promise<RPCEventResult<s.RPCEvents_Server, EventName>> {
return this.emitLocal(eventName, args)
}
// ===== OTHER =====
public onCommand<
CommandName extends s.RPCCommands_Server,
CallbackArguments extends unknown[],
>(
public onCommand<CommandName extends RPCCommandName<s.RPCCommands_Server>>(
command: CommandName,
cb: (player: number, args: CallbackArguments, commandRaw: string) => void,
cb: (player: number, args: string[], rawCommand: string) => void,
restricted = false,
): this {
this.log(`onCommand ${command}`)
@@ -197,10 +178,10 @@ export class RPCInstanceServer extends RPCInstanceBase {
return this
}
public onNativeEvent<
EventName extends keyof RPCNativeServerEvents,
CallbackArguments extends Parameters<RPCNativeServerEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
public onNativeEvent<EventName extends keyof RPCNativeServerEvents>(
eventName: EventName,
cb: (...args: Parameters<RPCNativeServerEvents[EventName]>) => void,
): this {
if (!NATIVE_SERVER_EVENTS.includes(eventName)) {
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
+33 -44
View File
@@ -9,6 +9,12 @@ import {
type RPCStateRaw,
type RPCStateWeb,
} from '../utils/types'
import type {
RPCEventArgs,
RPCEventName,
RPCEventResult,
RPCListener,
} from '../utils/typing'
import { RPCInstanceBase } from './base'
export class RPCInstanceWebview extends RPCInstanceBase {
@@ -64,93 +70,76 @@ export class RPCInstanceWebview extends RPCInstanceBase {
// ===== CLIENT =====
public onClient<
EventName extends keyof s.RPCEvents_ClientWebview,
CallbackArguments extends Parameters<s.RPCEvents_ClientWebview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ClientWebview[EventName]>,
>(
public onClient<EventName extends RPCEventName<s.RPCEvents_ClientWebview>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_ClientWebview, EventName>,
): this {
return this.listen(this._emitterClient, 'onClient', eventName, cb)
}
public offClient<EventName extends keyof s.RPCEvents_ClientWebview>(
public offClient<EventName extends RPCEventName<s.RPCEvents_ClientWebview>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterClient, 'offClient', eventName)
}
public async emitClient<
EventName extends keyof s.RPCEvents_WebviewClient,
Arguments extends Parameters<s.RPCEvents_WebviewClient[EventName]>,
Response extends ReturnType<s.RPCEvents_WebviewClient[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
EventName extends RPCEventName<s.RPCEvents_WebviewClient>,
>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_WebviewClient, EventName>
): Promise<RPCEventResult<s.RPCEvents_WebviewClient, EventName>> {
const payload = this.request(eventName, 'client', args, null)
return this._request<Awaited<Response>>(payload)
return this._request(payload)
}
// ===== SERVER =====
public onServer<
EventName extends keyof s.RPCEvents_ServerWebview,
CallbackArguments extends Parameters<s.RPCEvents_ServerWebview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ServerWebview[EventName]>,
>(
public onServer<EventName extends RPCEventName<s.RPCEvents_ServerWebview>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_ServerWebview, EventName>,
): this {
return this.listen(this._emitterServer, 'onServer', eventName, cb)
}
public offServer<EventName extends keyof s.RPCEvents_ServerWebview>(
public offServer<EventName extends RPCEventName<s.RPCEvents_ServerWebview>>(
eventName: EventName,
): RPCInstanceWebview {
): this {
return this.unlisten(this._emitterServer, 'offServer', eventName)
}
public async emitServer<
EventName extends keyof s.RPCEvents_WebviewServer,
Arguments extends Parameters<s.RPCEvents_WebviewServer[EventName]>,
Response extends ReturnType<s.RPCEvents_WebviewServer[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
EventName extends RPCEventName<s.RPCEvents_WebviewServer>,
>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_WebviewServer, EventName>
): Promise<RPCEventResult<s.RPCEvents_WebviewServer, EventName>> {
const payload = this.request(eventName, 'server', args, null)
return this._request<Awaited<Response>>(payload)
return this._request(payload)
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Webview,
CallbackArguments extends Parameters<s.RPCEvents_Webview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Webview[EventName]>,
>(
public onSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
cb: RPCListener<s.RPCEvents_Webview, EventName>,
): this {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
public offSelf<EventName extends keyof s.RPCEvents_Webview>(
public offSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Webview,
Arguments extends Parameters<s.RPCEvents_Webview[EventName]>,
Response extends ReturnType<s.RPCEvents_Webview[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
return this.emitLocal<Awaited<Response>>(eventName, args)
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Webview, EventName>
): Promise<RPCEventResult<s.RPCEvents_Webview, EventName>> {
return this.emitLocal(eventName, args)
}
// ===== UTILS =====
+47
View File
@@ -0,0 +1,47 @@
/**
* **Internal** Typing helpers over the event maps of
* `@entityseven/fivem-rpc-shared-types`. A map with declarations is typed
* strictly; an empty map (nothing declared, or the package not installed)
* accepts any name, any arguments and any result.
*/
// oxlint-disable-next-line typescript/no-explicit-any -- loose mode must accept listeners with any parameter types
type AnyEvent = (...args: any[]) => any
/** `true` for `any`, which is what the maps become if the package is missing */
type IsAny<T> = 0 extends 1 & T ? true : false
/** The declared map, or a loose one when nothing is declared */
type EventMap<T> =
IsAny<T> extends true
? Record<string, AnyEvent>
: [keyof T] extends [never]
? Record<string, AnyEvent>
: T
export type RPCEventName<T> = keyof EventMap<T> & string
export type RPCEventArgs<
T,
K extends RPCEventName<T>,
> = EventMap<T>[K] extends (...args: infer A extends unknown[]) => unknown
? A
: never
export type RPCEventResult<
T,
K extends RPCEventName<T>,
> = EventMap<T>[K] extends (...args: never[]) => infer R ? Awaited<R> : never
/** Listener for event `K` of map `T`; `Prefix` goes before the event arguments */
export type RPCListener<
T,
K extends RPCEventName<T>,
Prefix extends unknown[] = [],
> = (
...args: [...Prefix, ...RPCEventArgs<T, K>]
) => RPCEventResult<T, K> | Promise<RPCEventResult<T, K>>
export type RPCCommandName<T> = [keyof T] extends [never]
? string
: keyof T & string