From 67ea24ab3383f8f2412b2f2f851a78031fb8fe48 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 21:50:21 +0100 Subject: [PATCH 01/33] chore: remove bun.lock from .gitignore --- .gitignore | 1 - bun.lock | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 bun.lock diff --git a/.gitignore b/.gitignore index 341de79..8d4d731 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .vscode dist node_modules -bun.lock \ No newline at end of file diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..04c7721 --- /dev/null +++ b/bun.lock @@ -0,0 +1,354 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "devDependencies": { + "@biomejs/biome": "^2.1.1", + "@types/bun": "^1.2.18", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "rpc": { + "name": "@entityseven/fivem-rpc", + "version": "0.1.0", + "devDependencies": { + "@citizenfx/client": "^2.0.15015-1", + "@citizenfx/server": "^2.0.14862-1", + "@microsoft/api-extractor": "^7.47.9", + "tsup": "^8.3.0", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "shared-types": { + "name": "@entityseven/fivem-rpc-shared-types", + "version": "0.1.0", + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@biomejs/biome": ["@biomejs/biome@2.5.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.14", "@biomejs/cli-darwin-x64": "2.5.14", "@biomejs/cli-linux-arm64": "2.5.14", "@biomejs/cli-linux-arm64-musl": "2.5.14", "@biomejs/cli-linux-x64": "2.5.14", "@biomejs/cli-linux-x64-musl": "2.5.14", "@biomejs/cli-win32-arm64": "2.5.14", "@biomejs/cli-win32-x64": "2.5.14" }, "bin": { "biome": "bin/biome" } }, "sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ=="], + + "@citizenfx/client": ["@citizenfx/client@2.0.15015-1", "", {}, "sha512-gm0/fWM0/Wn5Hny+cnSRsBc73Opz1PPKS4LKCraIE477tQ0uGNVACoeTQqxRJz54Lu09KeA4cCfocVmefVAqUA=="], + + "@citizenfx/server": ["@citizenfx/server@2.0.14862-1", "", {}, "sha512-I6XnxIGBhskPe9S+q1OQLDqs6TYw1RhX06K6jUg+n0hGITYools6VBIIhdPe2uUlHA+76qJajQlV9ONeR3mDig=="], + + "@entityseven/fivem-rpc": ["@entityseven/fivem-rpc@workspace:rpc"], + + "@entityseven/fivem-rpc-shared-types": ["@entityseven/fivem-rpc-shared-types@workspace:shared-types"], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.59.2", "", { "dependencies": { "@microsoft/api-extractor-model": "7.33.13", "@microsoft/tsdoc": "~0.17.0", "@microsoft/tsdoc-config": "~0.18.2", "@rushstack/node-core-library": "5.24.2", "@rushstack/rig-package": "0.7.3", "@rushstack/terminal": "0.24.5", "@rushstack/ts-command-line": "5.3.15", "diff": "~8.0.2", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.7.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-hhkH6p2AmJtneC1cyxDTzUBg3DJHxvsCW5ciTrFSY8H9nTWB+lwa1nmmmTAqNFc9mufMATlH6hwdOI/Jc1wjfw=="], + + "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.13", "", { "dependencies": { "@microsoft/tsdoc": "~0.17.0", "@microsoft/tsdoc-config": "~0.18.2", "@rushstack/node-core-library": "5.24.2" } }, "sha512-OVL/dDvHstFJMJb2amgI7S9p9wAVMyrt18R2aSBOP9tVAARWiEJAM3wVsgxXymMOvnvKO/np9X7PeJpNZv3eLg=="], + + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.17.0", "", {}, "sha512-p68VexhnH7ojf3U27RdryUDqnKJgKaWUbCL96vJR7N0mqDuhKG5OIECk3sCPPdynxjk+yoVhRkScqtv5KQjzsw=="], + + "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.2", "", { "dependencies": { "@microsoft/tsdoc": "0.17.0", "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-4ymuLTQ9lFwJXK15f41iPcNHflymjk02Ltq5f5wo3fA02O1nHIGSc1TIZwzTSQZep+jXHM644jobt4Q8O3vtqg=="], + + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.5", "", { "os": "android", "cpu": "arm" }, "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.5", "", { "os": "android", "cpu": "arm64" }, "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.5", "", { "os": "none", "cpu": "arm64" }, "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw=="], + + "@rushstack/node-core-library": ["@rushstack/node-core-library@5.24.2", "", { "dependencies": { "ajv": "~8.20.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.7.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-sgSqy/lqce+uMrggF/wEIISIfpMX8pAXE5qSzGgDYFrWJkrsttrw/MzU4GFVNcbHSkPz5KbSh/A4/JSpDHoNQA=="], + + "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.2.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog=="], + + "@rushstack/rig-package": ["@rushstack/rig-package@0.7.3", "", { "dependencies": { "jju": "~1.4.0", "resolve": "~1.22.1" } }, "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA=="], + + "@rushstack/terminal": ["@rushstack/terminal@0.24.5", "", { "dependencies": { "@rushstack/node-core-library": "5.24.2", "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-AkbHjc48S7g4HkFO2wKHVMuJiZs/KaRI/rjcSpEj7o6SXgRYZrpXDE10NFa8ov/bxm4F5x+A8iqxkAGfEUTuTA=="], + + "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.3.15", "", { "dependencies": { "@rushstack/terminal": "0.24.5", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-9oUS23ebDfbl8ED6CANdQ3qqBc/xhrQvzhQAv61oN391FvNRww6RzdUbo97NEXaMm7lNEQsTjdE3zqtgiMv/eQ=="], + + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], + + "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.8", "", {}, "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "fs-extra": ["fs-extra@11.3.6", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], + + "is-core-module": ["is-core-module@2.17.0", "", { "dependencies": { "hasown": "^2.0.4" } }, "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA=="], + + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.63.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.5", "@rollup/rollup-android-arm64": "4.63.5", "@rollup/rollup-darwin-arm64": "4.63.5", "@rollup/rollup-darwin-x64": "4.63.5", "@rollup/rollup-freebsd-arm64": "4.63.5", "@rollup/rollup-freebsd-x64": "4.63.5", "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", "@rollup/rollup-linux-arm-musleabihf": "4.63.5", "@rollup/rollup-linux-arm64-gnu": "4.63.5", "@rollup/rollup-linux-arm64-musl": "4.63.5", "@rollup/rollup-linux-loong64-gnu": "4.63.5", "@rollup/rollup-linux-loong64-musl": "4.63.5", "@rollup/rollup-linux-ppc64-gnu": "4.63.5", "@rollup/rollup-linux-ppc64-musl": "4.63.5", "@rollup/rollup-linux-riscv64-gnu": "4.63.5", "@rollup/rollup-linux-riscv64-musl": "4.63.5", "@rollup/rollup-linux-s390x-gnu": "4.63.5", "@rollup/rollup-linux-x64-gnu": "4.63.5", "@rollup/rollup-linux-x64-musl": "4.63.5", "@rollup/rollup-openbsd-x64": "4.63.5", "@rollup/rollup-openharmony-arm64": "4.63.5", "@rollup/rollup-win32-arm64-msvc": "4.63.5", "@rollup/rollup-win32-ia32-msvc": "4.63.5", "@rollup/rollup-win32-x64-gnu": "4.63.5", "@rollup/rollup-win32-x64-msvc": "4.63.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "@rushstack/node-core-library/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "tsup/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + } +} From f5d390050ab73cf95f892567606650ab671b3090 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 21:54:26 +0100 Subject: [PATCH 02/33] chore(repo): config files to standartize commits --- .editorconfig | 13 +++++++++++++ .gitattributes | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c1020cb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = crlf +indent_style = tab +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +indent_style = space +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9f0ef0f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=crlf +bun.lock text eol=lf From 14c95dd3b43dc7628b2c46a777360ffc885f4b75 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:24:48 +0100 Subject: [PATCH 03/33] chore(repo): migrate biome to oxfmt + oxlint --- .oxfmtrc.json | 20 ++++ .oxlintrc.json | 14 +++ biome.json | 47 -------- bun.lock | 103 +++++++++++++--- package.json | 35 +++--- readme.md | 15 ++- rpc/package.json | 22 ++-- rpc/readme.md | 134 +++++++++++++++++++-- rpc/src/core/client.ts | 1 + rpc/src/core/server.ts | 1 + rpc/src/core/webview.ts | 1 + shared-types/package.json | 22 ++-- shared-types/readme.md | 239 ++++++++++++++++++++------------------ 13 files changed, 430 insertions(+), 224 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 biome.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..f071f3c --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,20 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "useTabs": true, + "tabWidth": 2, + "printWidth": 80, + "endOfLine": "crlf", + "singleQuote": true, + "semi": false, + "trailingComma": "all", + "arrowParens": "avoid", + "bracketSpacing": true, + "sortImports": true, + "ignorePatterns": ["**/dist", "bun.lock"], + "overrides": [ + { + "files": ["*.md"], + "options": { "embeddedLanguageFormatting": "off" } + } + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..7fe84d1 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,14 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "typescript", "unicorn", "oxc", "import"], + "categories": { + "correctness": "error", + "suspicious": "warn" + }, + "rules": { + "typescript/no-explicit-any": "warn", + "typescript/no-unsafe-function-type": "warn", + "eslint/no-underscore-dangle": "off" + }, + "ignorePatterns": ["**/dist"] +} diff --git a/biome.json b/biome.json deleted file mode 100644 index b0a61b1..0000000 --- a/biome.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "includes": ["**", "!dist/*"] - }, - "formatter": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "complexity": { - "noBannedTypes": "info" - }, - "suspicious": { - "noExplicitAny": "info" - } - } - }, - "javascript": { - "formatter": { - "arrowParentheses": "asNeeded", - "bracketSpacing": true, - "indentWidth": 2, - "lineEnding": "crlf", - "lineWidth": 80, - "quoteStyle": "single", - "semicolons": "asNeeded", - "trailingCommas": "all" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - } -} diff --git a/bun.lock b/bun.lock index 04c7721..4b23450 100644 --- a/bun.lock +++ b/bun.lock @@ -4,8 +4,9 @@ "workspaces": { "": { "devDependencies": { - "@biomejs/biome": "^2.1.1", "@types/bun": "^1.2.18", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", }, "peerDependencies": { "typescript": "^5", @@ -33,24 +34,6 @@ }, }, "packages": { - "@biomejs/biome": ["@biomejs/biome@2.5.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.14", "@biomejs/cli-darwin-x64": "2.5.14", "@biomejs/cli-linux-arm64": "2.5.14", "@biomejs/cli-linux-arm64-musl": "2.5.14", "@biomejs/cli-linux-x64": "2.5.14", "@biomejs/cli-linux-x64-musl": "2.5.14", "@biomejs/cli-win32-arm64": "2.5.14", "@biomejs/cli-win32-x64": "2.5.14" }, "bin": { "biome": "bin/biome" } }, "sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ=="], - "@citizenfx/client": ["@citizenfx/client@2.0.15015-1", "", {}, "sha512-gm0/fWM0/Wn5Hny+cnSRsBc73Opz1PPKS4LKCraIE477tQ0uGNVACoeTQqxRJz54Lu09KeA4cCfocVmefVAqUA=="], "@citizenfx/server": ["@citizenfx/server@2.0.14862-1", "", {}, "sha512-I6XnxIGBhskPe9S+q1OQLDqs6TYw1RhX06K6jUg+n0hGITYools6VBIIhdPe2uUlHA+76qJajQlV9ONeR3mDig=="], @@ -129,6 +112,82 @@ "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.70.0", "", { "os": "android", "cpu": "arm" }, "sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw=="], + + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.70.0", "", { "os": "android", "cpu": "arm64" }, "sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg=="], + + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.70.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA=="], + + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.70.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w=="], + + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.70.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA=="], + + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ=="], + + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA=="], + + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA=="], + + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg=="], + + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.70.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA=="], + + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ=="], + + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g=="], + + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.70.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg=="], + + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ=="], + + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA=="], + + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.70.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA=="], + + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.70.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q=="], + + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.70.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA=="], + + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.70.0", "", { "os": "win32", "cpu": "x64" }, "sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg=="], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.5", "", { "os": "android", "cpu": "arm" }, "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.5", "", { "os": "android", "cpu": "arm64" }, "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg=="], @@ -285,6 +344,10 @@ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "oxfmt": ["oxfmt@0.70.0", "", { "dependencies": { "tinypool": "2.1.2" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.70.0", "@oxfmt/binding-android-arm64": "0.70.0", "@oxfmt/binding-darwin-arm64": "0.70.0", "@oxfmt/binding-darwin-x64": "0.70.0", "@oxfmt/binding-freebsd-x64": "0.70.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.70.0", "@oxfmt/binding-linux-arm-musleabihf": "0.70.0", "@oxfmt/binding-linux-arm64-gnu": "0.70.0", "@oxfmt/binding-linux-arm64-musl": "0.70.0", "@oxfmt/binding-linux-ppc64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-musl": "0.70.0", "@oxfmt/binding-linux-s390x-gnu": "0.70.0", "@oxfmt/binding-linux-x64-gnu": "0.70.0", "@oxfmt/binding-linux-x64-musl": "0.70.0", "@oxfmt/binding-openharmony-arm64": "0.70.0", "@oxfmt/binding-win32-arm64-msvc": "0.70.0", "@oxfmt/binding-win32-ia32-msvc": "0.70.0", "@oxfmt/binding-win32-x64-msvc": "0.70.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw=="], + + "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -331,6 +394,8 @@ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + "tinypool": ["tinypool@2.1.2", "", {}, "sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww=="], + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], diff --git a/package.json b/package.json index a88323f..3ca7d63 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,5 @@ { - "workspaces": [ - "rpc", - "shared-types" - ], - "devDependencies": { - "@biomejs/biome": "^2.1.1", - "@types/bun": "^1.2.18" - }, - "peerDependencies": { - "typescript": "^5" - }, - "scripts": { - "check": "bunx biome check", - "format": "bunx biome format --write" - }, "private": true, - "type": "module", "license": "CC0-1.0", "author": "Entity Seven Group", "contributors": [ @@ -33,5 +17,24 @@ "repository": { "type": "git", "url": "https://github.com/rilaxik/fivem-rpc.git" + }, + "workspaces": [ + "rpc", + "shared-types" + ], + "type": "module", + "scripts": { + "check": "oxfmt --check && oxlint", + "format": "oxfmt", + "format:check": "oxfmt --check", + "lint": "oxlint" + }, + "devDependencies": { + "@types/bun": "^1.2.18", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0" + }, + "peerDependencies": { + "typescript": "^5" } } diff --git a/readme.md b/readme.md index 223ef90..ae6997d 100644 --- a/readme.md +++ b/readme.md @@ -1,41 +1,54 @@ # FiveM RPC + is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS ## Installation + ```bash pnpm i @entityseven/fivem-rpc ``` + ```bash yarn add @entityseven/fivem-rpc ``` + ```bash bun add @entityseven/fivem-rpc ``` + It is highly recommended to also install additional package for enhanced typing + ```bash pnpm i @entityseven/fivem-rpc-shared-types -D ``` + ```bash yarn add @entityseven/fivem-rpc-shared-types --dev ``` + ```bash bun add @entityseven/fivem-rpc-shared-types -d ``` ## Docs + Can be found in [/rpc/readme.md](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/readme.md) ## Features + - Type-Safe Development: Eliminate runtime errors and enhance code reliability with comprehensive type safety - All-in-one package: Communicate effortlessly between server, client and webview ## Contributing + Issues and pull requests are very welcome ## License + Licensed under Custom Attribution-NoDerivs Software License ## WIP + - client observers to catch events between server and webview (subscribe-like behaviour) - client observers to prevent events (middleware-like behaviour) -- player manager (transform player id to desired data straight from a listener) \ No newline at end of file +- player manager (transform player id to desired data straight from a listener) diff --git a/rpc/package.json b/rpc/package.json index 46ca8d6..d09e124 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -1,20 +1,14 @@ { "name": "@entityseven/fivem-rpc", - "description": "FiveM RPC is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS", "version": "0.1.0", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist/**/*", - "readme.md", - "license.md" - ], + "description": "FiveM RPC is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS", "keywords": [ + "fivem", "fivem-rpc", "fivem-rpc-shared-types", - "fivem", "gta" ], + "license": "Custom-Attribution-NoDerivs", "author": "Entity Seven Group", "contributors": [ { @@ -23,18 +17,24 @@ "url": "https://github.com/rilaxik/" } ], - "license": "Custom-Attribution-NoDerivs", "repository": { "type": "git", "url": "https://github.com/rilaxik/fivem-rpc.git" }, + "files": [ + "dist/**/*", + "readme.md", + "license.md" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "build": "tsup" }, "devDependencies": { - "@microsoft/api-extractor": "^7.47.9", "@citizenfx/client": "^2.0.15015-1", "@citizenfx/server": "^2.0.14862-1", + "@microsoft/api-extractor": "^7.47.9", "tsup": "^8.3.0" }, "peerDependencies": { diff --git a/rpc/readme.md b/rpc/readme.md index 577e648..bdc7f99 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -1,32 +1,43 @@ # FiveM RPC + is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS. [Extra info](https://github.com/rilaxik/fivem-rpc/blob/master/readme.md) # Motivation + The idea was to create an extensible package, with various features to simplify the development process and provide as much comfort as possible. Inspired by usage of [altv-xrpc](https://github.com/xxshady/altv-xrpc) # Installation + ```bash pnpm i @entityseven/fivem-rpc ``` + ```bash yarn add @entityseven/fivem-rpc ``` + ```bash bun add @entityseven/fivem-rpc ``` + It is highly recommended to also install additional package for enhanced typing + ```bash pnpm i @entityseven/fivem-rpc-shared-types -D ``` + ```bash yarn add @entityseven/fivem-rpc-shared-types --dev ``` + ```bash bun add @entityseven/fivem-rpc-shared-types -d ``` ## Usage + FiveM RPC is meant to be a singletone per environment. This means you _must create only one_ `RPCFactory` per your server/client/web. This also enables modifying `const rpc` to your needs, adding new methods or variables by forcing you to import it from file instead of library reference + ```ts // lib/rpc.ts import { RPCFactory } from '@entityseven/fivem-rpc' @@ -36,21 +47,25 @@ export const rpc = new RPCFactory(/* options */).get() # Docs ## Extras + Along with `RPCFactory` you can also import all the types used internally, types for native client/server events and lists of native client/server events. All of that is documented in JSDoc, so no need to duplicate it here ## RPCConfig + ```ts type RPCConfig = { - env: T + env: T debug?: boolean } ``` + Failing to set `env` to provided type will result in `RPCErrors.UNKNOWN_ENVIRONMENT` `debug` adds additional console logs to events - ## Errors + Known errors could be one of following or an error throw by a callback specifically + ```ts enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', @@ -62,7 +77,9 @@ enum RPCErrors { ``` ### Example error + Values wrapped in `<>` always exist, just not relevant for an example. Keep in mind that some errors are thrown in their destination(`To`) point: this example will throw on server + ``` Error: No player (failed to resolve from local index) Event: 'clientServerEvent' @@ -75,77 +92,113 @@ Data: [] ``` ## Server ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/server.ts)) + ### onClient + Listens to client event + ```ts rpc.onClient('clientServerEvent', (player, arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offClient + Stops listening to client event + ```ts rpc.offClient('clientServerEvent') ``` + ### emitClient + Sends event to specified client + ```ts -const response = await rpc.emitClient(playerServerId, 'serverClientEvent', someData) +const response = await rpc.emitClient(playerServerId, 'serverClientEvent', someData) // response will come from client listener with returned data ``` + ### emitClientEveryone + Sends event to all clients + ```ts rpc.emitClientEveryone('serverClientEvent', someData) ``` + ### onWebview + Listens to webview event + ```ts rpc.onWebview('webviewServerEvent', (player, arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offWebview + Stops listening to webview event + ```ts rpc.offWebview('webviewServerEvent') ``` + ### emitWebview + Sends event to specified webview + ```ts const response = await rpc.emitWebview(playerServerId, 'serverWebviewEvent', someData) // response will come from webview listener with returned data ``` + ### onSelf + Listens to server event + ```ts rpc.onSelf('serverEvent', (arg1, arg2, ...rest) => { - // logic + // logic return someData // this will be forwarded back to caller }) ``` + ### offSelf + Stops listening to server event + ```ts rpc.offSelf('serverEvent') ``` + ### emitSelf + Sends event to server + ```ts const response = await rpc.emitSelf('serverEvent', someData) // response will come from server listener with returned data ``` + ### onCommand + Registers chat command. Since arguments are untyped you must validate them yourself + ```ts rpc.onCommand('serverCommand', (player, args, commandRaw) => { // logic }) ``` + ### onNativeEvent + Listens to native server event ([reference](https://docs.fivem.net/docs/scripting-reference/events/server-events/)) + ```ts rpc.onNativeEvent('playerJoining', (source, oldId) => { // logic @@ -153,148 +206,215 @@ rpc.onNativeEvent('playerJoining', (source, oldId) => { ``` ## Client ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/client.ts)) + ### onServer + Listens to server event + ```ts rpc.onServer('serverClientEvent', (arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offServer + Stops listening to server event + ```ts rpc.offServer('serverClientEvent') ``` + ### emitServer + Sends event to server + ```ts const response = await rpc.emitServer('clientServerEvent', someData) // response will come from webview listener with returned data ``` + ### onWebview + Listens to webview event + ```ts rpc.onWebview('webviewClientEvent', (arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offWebview + Stops listening to webview event + ```ts rpc.offWebview('webviewClientEvent') ``` + ### emitWebview + Sends event to specified webview + ```ts const response = await rpc.emitWebview('clientWebviewEvent', someData) // response will come from webview listener with returned data ``` + ### onSelf + Listens to client event + ```ts rpc.onSelf('clientEvent', (arg1, arg2, ...rest) => { - // logic + // logic return someData // this will be forwarded back to caller }) ``` + ### offSelf + Stops listening to client event + ```ts rpc.offSelf('clientEvent') ``` + ### emitSelf + Sends event to client + ```ts const response = await rpc.emitSelf('clientEvent', someData) // response will come from client listener with returned data ``` + ### onCommand + Registers chat command. Since arguments are untyped you must validate them yourself + ```ts rpc.onCommand('clientCommand', (player, args, commandRaw) => { // logic }) ``` + ### onNativeEvent + Listens to native client event ([reference](https://docs.fivem.net/docs/scripting-reference/events/client-events/)) + ```ts rpc.onNativeEvent('entityDamaged', (victim, culprit, weapon, baseDamage) => { // logic }) ``` + ### onNativeNetworkEvent + Listens to native client network event ([reference](https://docs.fivem.net/docs/game-references/game-events/)) + ```ts rpc.onNativeNetworkEvent('CEventShockingCarCrash', (entities, eventEntity, data) => { // logic }) ``` + ### setWebviewFocus + Sets or removes focus and cursor from own webview + ```ts rpc.setWebviewFocus(true /* focus */, true /* show cursor */) ``` ## Webview ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/webview.ts)) + ### onClient + Listens to client event + ```ts rpc.onClient('clientWebviewEvent', (arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offClient + Stops listening to client event + ```ts rpc.offClient('clientWebviewEvent') ``` + ### emitClient + Sends event to own client + ```ts -const response = await rpc.emitClient('webviewClientEvent', someData) +const response = await rpc.emitClient('webviewClientEvent', someData) // response will come from client listener with returned data ``` + ### onServer + Listens to server event + ```ts rpc.onServer('serverWebviewEvent', (arg1, arg2, ...rest) => { // logic return someData // this will be forwarded back to caller }) ``` + ### offServer + Stops listening to server event + ```ts rpc.offServer('serverWebviewEvent') ``` + ### emitServer + Sends event to server + ```ts const response = await rpc.emitServer('webviewServerEvent', someData) // response will come from webview listener with returned data ``` + ### onSelf + Listens to webview event + ```ts rpc.onSelf('webviewEvent', (arg1, arg2, ...rest) => { - // logic + // logic return someData // this will be forwarded back to caller }) ``` + ### offSelf + Stops listening to webview event + ```ts rpc.offSelf('webviewEvent') ``` + ### emitSelf + Sends event to webview + ```ts const response = await rpc.emitSelf('webviewEvent', someData) // response will come from webview listener with returned data ``` # License + Licensed under Custom Attribution-NoDerivs Software License diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 1bc064d..dd1cc94 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -1,4 +1,5 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' + import { Emitter } from '../utils/emitter' import { generateUUID, parse, stringify, stringifyWeb } from '../utils/funcs' import { diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 3807217..4aa41ff 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -1,4 +1,5 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' + import { Emitter } from '../utils/emitter' import { generateUUID, parse, stringify } from '../utils/funcs' import { NATIVE_SERVER_EVENTS } from '../utils/native' diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index a92cb52..837fadb 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -1,4 +1,5 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' + import { Emitter } from '../utils/emitter' import { generateUUID, stringify } from '../utils/funcs' import { diff --git a/shared-types/package.json b/shared-types/package.json index 344657c..b618f90 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -1,20 +1,14 @@ { "name": "@entityseven/fivem-rpc-shared-types", - "description": "Shared (enhanced) types for @entityseven/fivem-rpc. Highly recommended to install together", "version": "0.1.0", - "types": "types/types/index.d.ts", - "files": [ - "types/**/*", - "readme.md", - "license.md" - ], + "description": "Shared (enhanced) types for @entityseven/fivem-rpc. Highly recommended to install together", "keywords": [ - "fivem-rpc-shared-types", - "fivem-rpc", "fivem", + "fivem-rpc", + "fivem-rpc-shared-types", "gta" ], - "type": "module", + "license": "Custom-Attribution-NoDerivs", "author": "Entity Seven Group", "contributors": [ { @@ -23,11 +17,17 @@ "url": "https://github.com/rilaxik/" } ], - "license": "Custom-Attribution-NoDerivs", "repository": { "type": "git", "url": "https://github.com/rilaxik/fivem-rpc.git" }, + "files": [ + "types/**/*", + "readme.md", + "license.md" + ], + "type": "module", + "types": "types/types/index.d.ts", "peerDependencies": { "typescript": "^5" } diff --git a/shared-types/readme.md b/shared-types/readme.md index 2a23bfd..31f08bf 100644 --- a/shared-types/readme.md +++ b/shared-types/readme.md @@ -1,22 +1,29 @@ # FiveM RPC Shared Types + ### [Docs & Info](https://github.com/rilaxik/fivem-rpc/blob/master/readme.md) ## Installation + ```bash pnpm i @entityseven/fivem-rpc-shared-types -D ``` + ```bash yarn add @entityseven/fivem-rpc-shared-types --dev ``` + ```bash bun add @entityseven/fivem-rpc-shared-types -d ``` ## Usage + This package is an enhanced type support for `@entityseven/fivem-rpc`. It provides ability to strictly type your events for better dx. ## Example + This example is neat way to follow up but you can change it as you wish. It will use bun workspaces, pnpm workspaces work in a similar manner. Referring the following folder structure: + ```markdown apps/ - server/ @@ -38,131 +45,139 @@ apps/ - Environment folder: folder with server, client or webview code in it 1. Install this package as dev dependency in your root package or in each environment folder separately - ```markdown - apps/ - - server/ <- (if not installed root) - - client/ <- (if not installed root) - - webview/ <- (if not installed root) - - shared/ - - - package.json <- here - ``` + + ```markdown + apps/ + - server/ <- (if not installed root) + - client/ <- (if not installed root) + - webview/ <- (if not installed root) + - shared/ + + - package.json <- here + ``` 2. In `shared/` create folder `fivem-rpc` (or similar), inside it create `index.d.ts` - ```markdown - apps/ - - server/ - - client/ - - webview/ - - shared/ - - fivem-rpc/ - - index.d.ts <- here - - - package.json - ``` + + ```markdown + apps/ + - server/ + - client/ + - webview/ + - shared/ + - fivem-rpc/ + - index.d.ts <- here + + - package.json + ``` 3. In `index.d.ts` add following: - ```ts - declare module '@entityseven/fivem-rpc-shared-types' { - // Client commands names - export type RPCCommands_Client = '' - - // Server commands names - export type RPCCommands_Server = '' - - // Client -> Client events - export interface RPCEvents_Client {} - - // Client -> Server events - export interface RPCEvents_ClientServer {} - - // Client -> Webview events - export interface RPCEvents_ClientWebview {} - - // Server -> Server events - export interface RPCEvents_Server {} - - // Server -> Client events - export interface RPCEvents_ServerClient {} - - // Server -> Server events - export interface RPCEvents_ServerWebview {} - - // Webview -> Webview events - export interface RPCEvents_Webview {} - - // Webview -> Client events - export interface RPCEvents_WebviewClient {} - - // Webview -> Server events - export interface RPCEvents_WebviewServer {} - } - ``` + + ```ts + declare module '@entityseven/fivem-rpc-shared-types' { + // Client commands names + export type RPCCommands_Client = '' + + // Server commands names + export type RPCCommands_Server = '' + + // Client -> Client events + export interface RPCEvents_Client {} + + // Client -> Server events + export interface RPCEvents_ClientServer {} + + // Client -> Webview events + export interface RPCEvents_ClientWebview {} + + // Server -> Server events + export interface RPCEvents_Server {} + + // Server -> Client events + export interface RPCEvents_ServerClient {} + + // Server -> Server events + export interface RPCEvents_ServerWebview {} + + // Webview -> Webview events + export interface RPCEvents_Webview {} + + // Webview -> Client events + export interface RPCEvents_WebviewClient {} + + // Webview -> Server events + export interface RPCEvents_WebviewServer {} + } + ``` 4. We just created a declaration which will overwrite types from the package. Now we need our packages to refer to these types when linting `rpc` functions. To do this in each environment folder of your project in `tsconfig.json` do these: - ```json5 - { - "compilerOptions": { - "types": [ - "../shared/fivem-rpc/" // or your specific folder - ] - } - } - ``` + ```json5 + { + "compilerOptions": { + "types": [ + "../shared/fivem-rpc/" // or your specific folder + ] + } + } + ``` 5. We also need to populate interfaces we created in step 3. + - `RPCCommands_Client` and `RPCCommands_Server` will include your commands names an union strings: - ```ts - export type RPCCommands_Client = 'afk' | 'vanish' | '...' // example names - export type RPCCommands_Server = 'report' | 'ban' | '...' // example names - ``` + ```ts + export type RPCCommands_Client = 'afk' | 'vanish' | '...' // example names + export type RPCCommands_Server = 'report' | 'ban' | '...' // example names + ``` - Other interfaces will include your events types. The example will show one but all of the work same way - ```ts - export interface RPCEvents_ClientServer { - clientToServerEventName(data: string, moreData: boolean): number - "client-to-server-event-name"(data: string, moreData: boolean): number // can also include characters you cannot use as variable or function names - } - ``` - - `clientToServerEventName` or `client-to-server-event-name` is an event name - - `data` and `moreData` are the arguments you need to pass when calling an event and argument you will receive when listening (may also include extra, as player, check type hints) - - `number` is a return type that will be forwarded back to caller - - Doing this will create type hints for you: - ```ts - // assuming this is in client - const response /* number */ = await rpc.emitServer( - 'clientToServerEventName', /* suggested name */ - 'data', /* will pass typecheck */ - 'moreData', /* will NOT pass typecheck, since required type is `boolean` */ - ) - ``` + + ```ts + export interface RPCEvents_ClientServer { + clientToServerEventName(data: string, moreData: boolean): number + "client-to-server-event-name"(data: string, moreData: boolean): number // can also include characters you cannot use as variable or function names + } + ``` + - `clientToServerEventName` or `client-to-server-event-name` is an event name + - `data` and `moreData` are the arguments you need to pass when calling an event and argument you will receive when listening (may also include extra, as player, check type hints) + - `number` is a return type that will be forwarded back to caller + + Doing this will create type hints for you: + + ```ts + // assuming this is in client + const response /* number */ = await rpc.emitServer( + 'clientToServerEventName', /* suggested name */ + 'data', /* will pass typecheck */ + 'moreData', /* will NOT pass typecheck, since required type is `boolean` */ + ) + ``` ## Example (alternative) + If previous example does not work or you do not like you can also try it this way. Steps that are not mentioned are the same as previous 2. In `shared/` create folders `declarations/fivem-rpc`, inside it create `index.d.ts` - ```markdown - apps/ - - server/ - - client/ - - webview/ - - shared/ - - declarations/ - - fivem-rpc/ - - index.d.ts <- here - - - package.json - ``` -4. We just created a declaration which will overwrite types from the package. Now we need our packages to refer to these types when linting `rpc` functions. To do this in each environment folder of your project in `tsconfig.json` do these: - ```json5 - { - "compilerOptions": { - "typeRoots": [ - "../shared/declarations/", // or your specific folder - "../../node_modules/@types", // you may also want to add this if some of your other libraries are not showing types now - ] - } - } - ``` + ```markdown + apps/ + - server/ + - client/ + - webview/ + - shared/ + - declarations/ + - fivem-rpc/ + - index.d.ts <- here -If a any point this stops working for you, do your research on how to redeclare library types and refer to it \ No newline at end of file + - package.json + ``` + +3. We just created a declaration which will overwrite types from the package. Now we need our packages to refer to these types when linting `rpc` functions. To do this in each environment folder of your project in `tsconfig.json` do these: + ```json5 + { + "compilerOptions": { + "typeRoots": [ + "../shared/declarations/", // or your specific folder + "../../node_modules/@types", // you may also want to add this if some of your other libraries are not showing types now + ] + } + } + ``` + +If a any point this stops working for you, do your research on how to redeclare library types and refer to it From 3b695eb56111e6d100789775b75442d571f0c464 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:25:46 +0100 Subject: [PATCH 04/33] chore(rpc): pass oxc checks --- rpc/src/core/client.ts | 2 +- rpc/src/core/server.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index dd1cc94..09b00ff 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -66,7 +66,7 @@ export class RPCInstanceClient extends Wrapper { private async _handleServer(payloadRaw: RPCStateRaw) { try { parse(payloadRaw) - } catch (e) { + } catch { throw new Error(RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 4aa41ff..26407f0 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -47,7 +47,7 @@ export class RPCInstanceServer extends Wrapper { private async _handleClient(payloadRaw: RPCStateRaw) { try { parse(payloadRaw) - } catch (e) { + } catch { throw new Error(RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) @@ -98,7 +98,7 @@ export class RPCInstanceServer extends Wrapper { private async _handleWeb(payloadRaw: RPCStateRaw) { try { parse(payloadRaw) - } catch (e) { + } catch { throw new Error(RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) From e6f9f07f4f000f4eb654f05745d19a236c240577 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:29:54 +0100 Subject: [PATCH 05/33] chore(repo): bump typescript version --- bun.lock | 57 +++++++++++++++++++++++++++++++-------- package.json | 13 +++++---- rpc/package.json | 2 +- shared-types/package.json | 2 +- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index 4b23450..c3625ea 100644 --- a/bun.lock +++ b/bun.lock @@ -4,12 +4,9 @@ "workspaces": { "": { "devDependencies": { - "@types/bun": "^1.2.18", "oxfmt": "^0.70.0", "oxlint": "^1.85.0", - }, - "peerDependencies": { - "typescript": "^5", + "typescript": "^7.0.2", }, }, "rpc": { @@ -22,14 +19,14 @@ "tsup": "^8.3.0", }, "peerDependencies": { - "typescript": "^5", + "typescript": ">=5", }, }, "shared-types": { "name": "@entityseven/fivem-rpc-shared-types", "version": "0.1.0", "peerDependencies": { - "typescript": "^5", + "typescript": ">=5", }, }, }, @@ -250,12 +247,50 @@ "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], - "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="], + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], @@ -272,8 +307,6 @@ "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], - "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], @@ -402,7 +435,7 @@ "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], @@ -410,6 +443,8 @@ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "@microsoft/api-extractor/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@rushstack/node-core-library/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], diff --git a/package.json b/package.json index 3ca7d63..f169a9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "license": "CC0-1.0", + "license": "SEE LICENSE IN license.md", "author": "Entity Seven Group", "contributors": [ { @@ -24,17 +24,16 @@ ], "type": "module", "scripts": { + "build": "bun run --filter '*' build", "check": "oxfmt --check && oxlint", "format": "oxfmt", "format:check": "oxfmt --check", - "lint": "oxlint" + "lint": "oxlint", + "typecheck": "bun run --filter '*' typecheck" }, "devDependencies": { - "@types/bun": "^1.2.18", "oxfmt": "^0.70.0", - "oxlint": "^1.85.0" - }, - "peerDependencies": { - "typescript": "^5" + "oxlint": "^1.85.0", + "typescript": "^7.0.2" } } diff --git a/rpc/package.json b/rpc/package.json index d09e124..3fb41fe 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -38,6 +38,6 @@ "tsup": "^8.3.0" }, "peerDependencies": { - "typescript": "^5" + "typescript": ">=5" } } diff --git a/shared-types/package.json b/shared-types/package.json index b618f90..47d41ba 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -29,6 +29,6 @@ "type": "module", "types": "types/types/index.d.ts", "peerDependencies": { - "typescript": "^5" + "typescript": ">=5" } } From b2d086e18e1b51cb243e723016fbd32395a3ba5d Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:31:58 +0100 Subject: [PATCH 06/33] chore(rpc): package.json maintenance --- bun.lock | 12 ++++++------ rpc/package.json | 23 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/bun.lock b/bun.lock index c3625ea..9ba5151 100644 --- a/bun.lock +++ b/bun.lock @@ -13,14 +13,18 @@ "name": "@entityseven/fivem-rpc", "version": "0.1.0", "devDependencies": { - "@citizenfx/client": "^2.0.15015-1", - "@citizenfx/server": "^2.0.14862-1", + "@entityseven/fivem-rpc-shared-types": "workspace:*", "@microsoft/api-extractor": "^7.47.9", "tsup": "^8.3.0", }, "peerDependencies": { + "@entityseven/fivem-rpc-shared-types": "^1.0.0", "typescript": ">=5", }, + "optionalPeers": [ + "@entityseven/fivem-rpc-shared-types", + "typescript", + ], }, "shared-types": { "name": "@entityseven/fivem-rpc-shared-types", @@ -31,10 +35,6 @@ }, }, "packages": { - "@citizenfx/client": ["@citizenfx/client@2.0.15015-1", "", {}, "sha512-gm0/fWM0/Wn5Hny+cnSRsBc73Opz1PPKS4LKCraIE477tQ0uGNVACoeTQqxRJz54Lu09KeA4cCfocVmefVAqUA=="], - - "@citizenfx/server": ["@citizenfx/server@2.0.14862-1", "", {}, "sha512-I6XnxIGBhskPe9S+q1OQLDqs6TYw1RhX06K6jUg+n0hGITYools6VBIIhdPe2uUlHA+76qJajQlV9ONeR3mDig=="], - "@entityseven/fivem-rpc": ["@entityseven/fivem-rpc@workspace:rpc"], "@entityseven/fivem-rpc-shared-types": ["@entityseven/fivem-rpc-shared-types@workspace:shared-types"], diff --git a/rpc/package.json b/rpc/package.json index 3fb41fe..f371476 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -8,13 +8,18 @@ "fivem-rpc-shared-types", "gta" ], - "license": "Custom-Attribution-NoDerivs", + "license": "SEE LICENSE IN license.md", "author": "Entity Seven Group", "contributors": [ { "name": "Danya H", "email": "dev.rilaxik@gmail.com", "url": "https://github.com/rilaxik/" + }, + { + "name": "Oleksandr Honcharov", + "email": "0976053529@ukr.net", + "url": "https://github.com/SashaGoncharov19/" } ], "repository": { @@ -29,15 +34,25 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "tsup" + "build": "tsup", + "prepublishOnly": "bun run build", + "typecheck": "tsc --noEmit" }, "devDependencies": { - "@citizenfx/client": "^2.0.15015-1", - "@citizenfx/server": "^2.0.14862-1", + "@entityseven/fivem-rpc-shared-types": "workspace:*", "@microsoft/api-extractor": "^7.47.9", "tsup": "^8.3.0" }, "peerDependencies": { + "@entityseven/fivem-rpc-shared-types": "^1.0.0", "typescript": ">=5" + }, + "peerDependenciesMeta": { + "@entityseven/fivem-rpc-shared-types": { + "optional": true + }, + "typescript": { + "optional": true + } } } From e88af4aa010e868a350c7a07b647625e6dd6c713 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:34:38 +0100 Subject: [PATCH 07/33] chore(rpc): tsconfig.json maintenance --- rpc/tsconfig.json | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/rpc/tsconfig.json b/rpc/tsconfig.json index eb2d78a..348e028 100644 --- a/rpc/tsconfig.json +++ b/rpc/tsconfig.json @@ -1,19 +1,15 @@ { "compilerOptions": { - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "lib": ["ES6", "dom"], - "declaration": true, - "declarationMap": true, - "sourceMap": true, + "target": "es2022", + "lib": ["es2022", "dom"], + "module": "preserve", + "moduleResolution": "bundler", - "outDir": "dist", - "esModuleInterop": true, + "noEmit": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": true + "strict": true }, - "include": ["src/**/*"] + "include": ["src"] } From b39a1d6e1b29f9bbb225619d6aae96ecd0c60497 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:38:18 +0100 Subject: [PATCH 08/33] chore(repo): migrate from tsup to tsdown --- bun.lock | 301 +++++++++++-------------------------------- rpc/package.json | 18 ++- rpc/tsdown.config.ts | 11 ++ rpc/tsup.config.ts | 14 -- 4 files changed, 97 insertions(+), 247 deletions(-) create mode 100644 rpc/tsdown.config.ts delete mode 100644 rpc/tsup.config.ts diff --git a/bun.lock b/bun.lock index 9ba5151..49e8555 100644 --- a/bun.lock +++ b/bun.lock @@ -14,8 +14,7 @@ "version": "0.1.0", "devDependencies": { "@entityseven/fivem-rpc-shared-types": "workspace:*", - "@microsoft/api-extractor": "^7.47.9", - "tsup": "^8.3.0", + "tsdown": "^0.23.0", }, "peerDependencies": { "@entityseven/fivem-rpc-shared-types": "^1.0.0", @@ -39,75 +38,7 @@ "@entityseven/fivem-rpc-shared-types": ["@entityseven/fivem-rpc-shared-types@workspace:shared-types"], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@microsoft/api-extractor": ["@microsoft/api-extractor@7.59.2", "", { "dependencies": { "@microsoft/api-extractor-model": "7.33.13", "@microsoft/tsdoc": "~0.17.0", "@microsoft/tsdoc-config": "~0.18.2", "@rushstack/node-core-library": "5.24.2", "@rushstack/rig-package": "0.7.3", "@rushstack/terminal": "0.24.5", "@rushstack/ts-command-line": "5.3.15", "diff": "~8.0.2", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.7.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-hhkH6p2AmJtneC1cyxDTzUBg3DJHxvsCW5ciTrFSY8H9nTWB+lwa1nmmmTAqNFc9mufMATlH6hwdOI/Jc1wjfw=="], - - "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.13", "", { "dependencies": { "@microsoft/tsdoc": "~0.17.0", "@microsoft/tsdoc-config": "~0.18.2", "@rushstack/node-core-library": "5.24.2" } }, "sha512-OVL/dDvHstFJMJb2amgI7S9p9wAVMyrt18R2aSBOP9tVAARWiEJAM3wVsgxXymMOvnvKO/np9X7PeJpNZv3eLg=="], - - "@microsoft/tsdoc": ["@microsoft/tsdoc@0.17.0", "", {}, "sha512-p68VexhnH7ojf3U27RdryUDqnKJgKaWUbCL96vJR7N0mqDuhKG5OIECk3sCPPdynxjk+yoVhRkScqtv5KQjzsw=="], - - "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.2", "", { "dependencies": { "@microsoft/tsdoc": "0.17.0", "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-4ymuLTQ9lFwJXK15f41iPcNHflymjk02Ltq5f5wo3fA02O1nHIGSc1TIZwzTSQZep+jXHM644jobt4Q8O3vtqg=="], - - "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@oxc-project/types": ["@oxc-project/types@0.151.0", "", {}, "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA=="], "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.70.0", "", { "os": "android", "cpu": "arm" }, "sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw=="], @@ -185,71 +116,39 @@ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.5", "", { "os": "android", "cpu": "arm" }, "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg=="], + "@quansync/fs": ["@quansync/fs@1.1.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.5", "", { "os": "android", "cpu": "arm64" }, "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.11", "", { "os": "android", "cpu": "arm" }, "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.11", "", { "os": "android", "cpu": "arm64" }, "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.11", "", { "os": "linux", "cpu": "arm" }, "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.11", "", { "os": "none", "cpu": "arm64" }, "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.5", "", { "os": "none", "cpu": "arm64" }, "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw=="], - - "@rushstack/node-core-library": ["@rushstack/node-core-library@5.24.2", "", { "dependencies": { "ajv": "~8.20.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.7.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-sgSqy/lqce+uMrggF/wEIISIfpMX8pAXE5qSzGgDYFrWJkrsttrw/MzU4GFVNcbHSkPz5KbSh/A4/JSpDHoNQA=="], - - "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.2.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog=="], - - "@rushstack/rig-package": ["@rushstack/rig-package@0.7.3", "", { "dependencies": { "jju": "~1.4.0", "resolve": "~1.22.1" } }, "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA=="], - - "@rushstack/terminal": ["@rushstack/terminal@0.24.5", "", { "dependencies": { "@rushstack/node-core-library": "5.24.2", "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-AkbHjc48S7g4HkFO2wKHVMuJiZs/KaRI/rjcSpEj7o6SXgRYZrpXDE10NFa8ov/bxm4F5x+A8iqxkAGfEUTuTA=="], - - "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.3.15", "", { "dependencies": { "@rushstack/terminal": "0.24.5", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-9oUS23ebDfbl8ED6CANdQ3qqBc/xhrQvzhQAv61oN391FvNRww6RzdUbo97NEXaMm7lNEQsTjdE3zqtgiMv/eQ=="], - - "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], - - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - - "@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], @@ -291,139 +190,89 @@ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], - "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + "@yuku-codegen/binding-android-arm64": ["@yuku-codegen/binding-android-arm64@0.10.2", "", { "os": "android", "cpu": "arm64" }, "sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g=="], - "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + "@yuku-codegen/binding-darwin-arm64": ["@yuku-codegen/binding-darwin-arm64@0.10.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A=="], - "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + "@yuku-codegen/binding-darwin-x64": ["@yuku-codegen/binding-darwin-x64@0.10.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA=="], - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "@yuku-codegen/binding-freebsd-x64": ["@yuku-codegen/binding-freebsd-x64@0.10.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "@yuku-codegen/binding-linux-arm-gnu": ["@yuku-codegen/binding-linux-arm-gnu@0.10.2", "", { "os": "linux", "cpu": "arm" }, "sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A=="], - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "@yuku-codegen/binding-linux-arm-musl": ["@yuku-codegen/binding-linux-arm-musl@0.10.2", "", { "os": "linux", "cpu": "arm" }, "sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "@yuku-codegen/binding-linux-arm64-gnu": ["@yuku-codegen/binding-linux-arm64-gnu@0.10.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw=="], - "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + "@yuku-codegen/binding-linux-arm64-musl": ["@yuku-codegen/binding-linux-arm64-musl@0.10.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA=="], - "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + "@yuku-codegen/binding-linux-x64-gnu": ["@yuku-codegen/binding-linux-x64-gnu@0.10.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "@yuku-codegen/binding-linux-x64-musl": ["@yuku-codegen/binding-linux-x64-musl@0.10.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q=="], - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "@yuku-codegen/binding-win32-arm64": ["@yuku-codegen/binding-win32-arm64@0.10.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw=="], - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "@yuku-codegen/binding-win32-x64": ["@yuku-codegen/binding-win32-x64@0.10.2", "", { "os": "win32", "cpu": "x64" }, "sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA=="], - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "@yuku-parser/binding-android-arm64": ["@yuku-parser/binding-android-arm64@0.10.2", "", { "os": "android", "cpu": "arm64" }, "sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw=="], - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "@yuku-parser/binding-darwin-arm64": ["@yuku-parser/binding-darwin-arm64@0.10.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "@yuku-parser/binding-darwin-x64": ["@yuku-parser/binding-darwin-x64@0.10.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ=="], - "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + "@yuku-parser/binding-freebsd-x64": ["@yuku-parser/binding-freebsd-x64@0.10.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ=="], - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "@yuku-parser/binding-linux-arm-gnu": ["@yuku-parser/binding-linux-arm-gnu@0.10.2", "", { "os": "linux", "cpu": "arm" }, "sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA=="], - "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "@yuku-parser/binding-linux-arm-musl": ["@yuku-parser/binding-linux-arm-musl@0.10.2", "", { "os": "linux", "cpu": "arm" }, "sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg=="], - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "@yuku-parser/binding-linux-arm64-gnu": ["@yuku-parser/binding-linux-arm64-gnu@0.10.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ=="], - "fast-uri": ["fast-uri@3.1.8", "", {}, "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg=="], + "@yuku-parser/binding-linux-arm64-musl": ["@yuku-parser/binding-linux-arm64-musl@0.10.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A=="], + + "@yuku-parser/binding-linux-x64-gnu": ["@yuku-parser/binding-linux-x64-gnu@0.10.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg=="], + + "@yuku-parser/binding-linux-x64-musl": ["@yuku-parser/binding-linux-x64-musl@0.10.2", "", { "os": "linux", "cpu": "x64" }, "sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w=="], + + "@yuku-parser/binding-win32-arm64": ["@yuku-parser/binding-win32-arm64@0.10.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw=="], + + "@yuku-parser/binding-win32-x64": ["@yuku-parser/binding-win32-x64@0.10.2", "", { "os": "win32", "cpu": "x64" }, "sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA=="], + + "@yuku-toolchain/types": ["@yuku-toolchain/types@0.10.2", "", {}, "sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q=="], + + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "dts-resolver": ["dts-resolver@3.0.0", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q=="], + + "empathic": ["empathic@2.1.0", "", {}, "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "get-tsconfig": ["get-tsconfig@5.0.0-beta.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw=="], - "fs-extra": ["fs-extra@11.3.6", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA=="], + "hookable": ["hookable@6.1.2", "", {}, "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "import-without-cache": ["import-without-cache@0.4.1", "", {}, "sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - - "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], - - "is-core-module": ["is-core-module@2.17.0", "", { "dependencies": { "hasown": "^2.0.4" } }, "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA=="], - - "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], - - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], - - "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "obug": ["obug@2.2.1", "", {}, "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q=="], "oxfmt": ["oxfmt@0.70.0", "", { "dependencies": { "tinypool": "2.1.2" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.70.0", "@oxfmt/binding-android-arm64": "0.70.0", "@oxfmt/binding-darwin-arm64": "0.70.0", "@oxfmt/binding-darwin-x64": "0.70.0", "@oxfmt/binding-freebsd-x64": "0.70.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.70.0", "@oxfmt/binding-linux-arm-musleabihf": "0.70.0", "@oxfmt/binding-linux-arm64-gnu": "0.70.0", "@oxfmt/binding-linux-arm64-musl": "0.70.0", "@oxfmt/binding-linux-ppc64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-musl": "0.70.0", "@oxfmt/binding-linux-s390x-gnu": "0.70.0", "@oxfmt/binding-linux-x64-gnu": "0.70.0", "@oxfmt/binding-linux-x64-musl": "0.70.0", "@oxfmt/binding-openharmony-arm64": "0.70.0", "@oxfmt/binding-win32-arm64-msvc": "0.70.0", "@oxfmt/binding-win32-ia32-msvc": "0.70.0", "@oxfmt/binding-win32-x64-msvc": "0.70.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw=="], "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + "rolldown": ["rolldown@1.2.11", "", { "dependencies": { "@oxc-project/types": "=0.151.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.11", "@rolldown/binding-android-arm64": "1.2.11", "@rolldown/binding-darwin-arm64": "1.2.11", "@rolldown/binding-darwin-x64": "1.2.11", "@rolldown/binding-freebsd-x64": "1.2.11", "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", "@rolldown/binding-linux-arm64-gnu": "1.2.11", "@rolldown/binding-linux-arm64-musl": "1.2.11", "@rolldown/binding-linux-ppc64-gnu": "1.2.11", "@rolldown/binding-linux-s390x-gnu": "1.2.11", "@rolldown/binding-linux-x64-gnu": "1.2.11", "@rolldown/binding-linux-x64-musl": "1.2.11", "@rolldown/binding-openharmony-arm64": "1.2.11", "@rolldown/binding-win32-arm64-msvc": "1.2.11", "@rolldown/binding-win32-x64-msvc": "1.2.11" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.28.6", "", { "dependencies": { "dts-resolver": "^3.0.0", "get-tsconfig": "5.0.0-beta.6", "obug": "^3.0.0", "yuku-ast": "^0.10.1", "yuku-codegen": "^0.10.1", "yuku-parser": "^0.10.1" }, "peerDependencies": { "@typescript/native-preview": "*", "@volar/typescript": "~2.4.0", "@vue/language-core": "~3.2.0 || ~3.3.0", "rolldown": "^1.2.0", "typescript": "^5.0.0 || ^6.0.0 || ~7.0.0", "vue-tsc": "~3.2.0 || ~3.3.0" }, "optionalPeers": ["@typescript/native-preview", "@volar/typescript", "@vue/language-core", "typescript", "vue-tsc"] }, "sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ=="], - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "rollup": ["rollup@4.63.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.5", "@rollup/rollup-android-arm64": "4.63.5", "@rollup/rollup-darwin-arm64": "4.63.5", "@rollup/rollup-darwin-x64": "4.63.5", "@rollup/rollup-freebsd-arm64": "4.63.5", "@rollup/rollup-freebsd-x64": "4.63.5", "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", "@rollup/rollup-linux-arm-musleabihf": "4.63.5", "@rollup/rollup-linux-arm64-gnu": "4.63.5", "@rollup/rollup-linux-arm64-musl": "4.63.5", "@rollup/rollup-linux-loong64-gnu": "4.63.5", "@rollup/rollup-linux-loong64-musl": "4.63.5", "@rollup/rollup-linux-ppc64-gnu": "4.63.5", "@rollup/rollup-linux-ppc64-musl": "4.63.5", "@rollup/rollup-linux-riscv64-gnu": "4.63.5", "@rollup/rollup-linux-riscv64-musl": "4.63.5", "@rollup/rollup-linux-s390x-gnu": "4.63.5", "@rollup/rollup-linux-x64-gnu": "4.63.5", "@rollup/rollup-linux-x64-musl": "4.63.5", "@rollup/rollup-openbsd-x64": "4.63.5", "@rollup/rollup-openharmony-arm64": "4.63.5", "@rollup/rollup-win32-arm64-msvc": "4.63.5", "@rollup/rollup-win32-ia32-msvc": "4.63.5", "@rollup/rollup-win32-x64-gnu": "4.63.5", "@rollup/rollup-win32-x64-msvc": "4.63.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw=="], - - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], - - "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], @@ -431,24 +280,20 @@ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + "tsdown": ["tsdown@0.23.0", "", { "dependencies": { "cac": "^7.0.0", "defu": "^6.1.7", "empathic": "^2.0.1", "hookable": "^6.1.1", "import-without-cache": "^0.4.0", "obug": "^2.1.4", "picomatch": "^4.0.7", "rolldown": "~1.2.7", "rolldown-plugin-dts": "^0.28.5", "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0", "verkit": "^0.4.0" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.23.0", "@tsdown/exe": "0.23.0", "@vitejs/devtools": "*", "publint": "^0.3.8", "tsx": "*", "typescript": "^5.0.0 || ^6.0.0 || ^7.0.0", "unplugin-unused": ">=0.5.0", "unrun": "*" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "tsx", "typescript", "unplugin-unused", "unrun"], "bin": { "tsdown": "./dist/run.mjs" } }, "sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q=="], "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], - "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], - "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + "verkit": ["verkit@0.4.1", "", {}, "sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ=="], - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "yuku-ast": ["yuku-ast@0.10.2", "", { "dependencies": { "@yuku-toolchain/types": "^0.10.2" } }, "sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ=="], - "@microsoft/api-extractor/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "yuku-codegen": ["yuku-codegen@0.10.2", "", { "dependencies": { "@yuku-toolchain/types": "^0.10.2" }, "optionalDependencies": { "@yuku-codegen/binding-android-arm64": "0.10.2", "@yuku-codegen/binding-darwin-arm64": "0.10.2", "@yuku-codegen/binding-darwin-x64": "0.10.2", "@yuku-codegen/binding-freebsd-x64": "0.10.2", "@yuku-codegen/binding-linux-arm-gnu": "0.10.2", "@yuku-codegen/binding-linux-arm-musl": "0.10.2", "@yuku-codegen/binding-linux-arm64-gnu": "0.10.2", "@yuku-codegen/binding-linux-arm64-musl": "0.10.2", "@yuku-codegen/binding-linux-x64-gnu": "0.10.2", "@yuku-codegen/binding-linux-x64-musl": "0.10.2", "@yuku-codegen/binding-win32-arm64": "0.10.2", "@yuku-codegen/binding-win32-x64": "0.10.2" } }, "sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA=="], - "@rushstack/node-core-library/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "yuku-parser": ["yuku-parser@0.10.2", "", { "dependencies": { "@yuku-toolchain/types": "^0.10.2", "yuku-ast": "^0.10.2" }, "optionalDependencies": { "@yuku-parser/binding-android-arm64": "0.10.2", "@yuku-parser/binding-darwin-arm64": "0.10.2", "@yuku-parser/binding-darwin-x64": "0.10.2", "@yuku-parser/binding-freebsd-x64": "0.10.2", "@yuku-parser/binding-linux-arm-gnu": "0.10.2", "@yuku-parser/binding-linux-arm-musl": "0.10.2", "@yuku-parser/binding-linux-arm64-gnu": "0.10.2", "@yuku-parser/binding-linux-arm64-musl": "0.10.2", "@yuku-parser/binding-linux-x64-gnu": "0.10.2", "@yuku-parser/binding-linux-x64-musl": "0.10.2", "@yuku-parser/binding-win32-arm64": "0.10.2", "@yuku-parser/binding-win32-x64": "0.10.2" } }, "sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A=="], - "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "tsup/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "rolldown-plugin-dts/obug": ["obug@3.0.0", "", {}, "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw=="], } } diff --git a/rpc/package.json b/rpc/package.json index f371476..90d310e 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -31,17 +31,25 @@ "readme.md", "license.md" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.cts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "./package.json": "./package.json" + }, "scripts": { - "build": "tsup", + "build": "tsdown", "prepublishOnly": "bun run build", "typecheck": "tsc --noEmit" }, "devDependencies": { "@entityseven/fivem-rpc-shared-types": "workspace:*", - "@microsoft/api-extractor": "^7.47.9", - "tsup": "^8.3.0" + "tsdown": "^0.23.0" }, "peerDependencies": { "@entityseven/fivem-rpc-shared-types": "^1.0.0", diff --git a/rpc/tsdown.config.ts b/rpc/tsdown.config.ts new file mode 100644 index 0000000..470b01a --- /dev/null +++ b/rpc/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + platform: 'neutral', + target: 'es2022', + fixedExtension: true, + dts: true, + exports: true, +}) diff --git a/rpc/tsup.config.ts b/rpc/tsup.config.ts deleted file mode 100644 index c91be33..0000000 --- a/rpc/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'tsup' - -export default defineConfig({ - entry: ['src/index.ts'], - outDir: './dist', - target: 'node16', - platform: 'node', - format: ['cjs'], - splitting: false, - sourcemap: false, - clean: false, - experimentalDts: true, - noExternal: [/.*/], -}) From 75cbdb512c756e07f7ed92b52563e1b0894045e7 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:39:35 +0100 Subject: [PATCH 09/33] chore(repo): package.json maintenance --- bun.lock | 3 +++ rpc/package.json | 3 ++- shared-types/package.json | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 49e8555..935ca86 100644 --- a/bun.lock +++ b/bun.lock @@ -31,6 +31,9 @@ "peerDependencies": { "typescript": ">=5", }, + "optionalPeers": [ + "typescript", + ], }, }, "packages": { diff --git a/rpc/package.json b/rpc/package.json index 90d310e..89bc1ad 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -24,7 +24,8 @@ ], "repository": { "type": "git", - "url": "https://github.com/rilaxik/fivem-rpc.git" + "url": "https://github.com/rilaxik/fivem-rpc.git", + "directory": "rpc" }, "files": [ "dist/**/*", diff --git a/shared-types/package.json b/shared-types/package.json index 47d41ba..3027291 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -8,18 +8,24 @@ "fivem-rpc-shared-types", "gta" ], - "license": "Custom-Attribution-NoDerivs", + "license": "SEE LICENSE IN license.md", "author": "Entity Seven Group", "contributors": [ { "name": "Danya H", "email": "dev.rilaxik@gmail.com", "url": "https://github.com/rilaxik/" + }, + { + "name": "Oleksandr Honcharov", + "email": "0976053529@ukr.net", + "url": "https://github.com/SashaGoncharov19/" } ], "repository": { "type": "git", - "url": "https://github.com/rilaxik/fivem-rpc.git" + "url": "https://github.com/rilaxik/fivem-rpc.git", + "directory": "shared-types" }, "files": [ "types/**/*", @@ -30,5 +36,10 @@ "types": "types/types/index.d.ts", "peerDependencies": { "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } } From 2f59e4c253bd341e82a75d439790c1917b349f0a Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:43:39 +0100 Subject: [PATCH 10/33] fix(rpc): move injected @citizenfx/* types to separate file --- rpc/src/core/client.ts | 14 -------------- rpc/src/core/server.ts | 9 --------- rpc/src/core/webview.ts | 6 ------ rpc/src/fivem.d.ts | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 rpc/src/fivem.d.ts diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 09b00ff..12d5a55 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -18,20 +18,6 @@ import { } from '../utils/types' import { Wrapper } from './wrapper' -declare function onNet(eventName: string, callback: Function): void -declare function emitNet(eventName: string, ...args: unknown[]): void -declare function on(eventName: string, callback: Function): void -declare function RegisterNuiCallbackType(callbackType: string): void -declare function GetPlayerServerId(player: number): number -declare function PlayerId(): number -declare function SetNuiFocus(hasFocus: boolean, hasCursor: boolean): void -declare function RegisterCommand( - commandName: string, - handler: Function, - restricted: boolean, -): void -declare function SendNuiMessage(jsonString: string): boolean - export class RPCInstanceClient extends Wrapper { private readonly _emitterServer: Emitter private readonly _pendingServer: Emitter diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 26407f0..8cb72fc 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -13,15 +13,6 @@ import { } from '../utils/types' import { Wrapper } from './wrapper' -declare function onNet(eventName: string, callback: Function): void -declare function emitNet(eventName: string, ...args: unknown[]): void -declare function on(eventName: string, callback: Function): void -declare function RegisterCommand( - commandName: string, - handler: Function, - restricted: boolean, -): void - export class RPCInstanceServer extends Wrapper { private readonly _emitterClient: Emitter private readonly _pendingClient: Emitter diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 837fadb..4f0a1d5 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -11,12 +11,6 @@ import { } from '../utils/types' import { Wrapper } from './wrapper' -declare global { - interface Window { - GetParentResourceName?: () => string - } -} - export class RPCInstanceWebview extends Wrapper { private readonly _emitterClient: Emitter private readonly _emitterServer: Emitter diff --git a/rpc/src/fivem.d.ts b/rpc/src/fivem.d.ts new file mode 100644 index 0000000..3d9a00a --- /dev/null +++ b/rpc/src/fivem.d.ts @@ -0,0 +1,36 @@ +/* +FiveM runtime globals used by this library. Internal: not part of the published types, so consumers are free to use @citizenfx/*. +See https://docs.fivem.net/docs/scripting-reference/runtimes/javascript/ +*/ + +// ===== SHARED (client + server) ===== + +declare function on( + eventName: string, + callback: (...args: A) => unknown, +): void +declare function onNet( + eventName: string, + callback: (...args: A) => unknown, +): void +/** Server: `emitNet(eventName, target, ...args)` */ +declare function emitNet(eventName: string, ...args: unknown[]): void +declare function RegisterCommand( + commandName: string, + handler: (source: number, args: A, rawCommand: string) => unknown, + restricted: boolean, +): void + +// ===== CLIENT ===== + +declare function RegisterNuiCallbackType(callbackType: string): void +declare function GetPlayerServerId(player: number): number +declare function PlayerId(): number +declare function SetNuiFocus(hasFocus: boolean, hasCursor: boolean): void +declare function SendNuiMessage(jsonString: string): boolean + +// ===== WEBVIEW (NUI) ===== + +interface Window { + GetParentResourceName?: () => string +} From 46933c1656c7a3fdb72687aaf5c259de6f7a0726 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:47:38 +0100 Subject: [PATCH 11/33] fix(rpc): rewrite Emitter --- rpc/src/utils/emitter.ts | 50 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/rpc/src/utils/emitter.ts b/rpc/src/utils/emitter.ts index 9adca71..76a9882 100644 --- a/rpc/src/utils/emitter.ts +++ b/rpc/src/utils/emitter.ts @@ -1,23 +1,22 @@ import { RPCErrors } from './types' +/** + * Accepts any callback. Argument types are enforced by the typed `on*`/`emit*` + * methods that wrap the emitter, not here. + */ +type Handler = (...args: never[]) => unknown + +/** One handler per event: registering an event again replaces its handler. */ export class Emitter { - /** Map */ - private _storage: Map any, boolean]> + /** Map */ + private _storage = new Map() - constructor() { - this._storage = new Map() - } - - get _raw_storage() { - return this._storage - } - - public on(event: string, cb: (...args: any[]) => any): this { + public on(event: string, cb: Handler): this { this._storage.set(event, [cb, false]) return this } - public once(event: string, cb: (...args: any[]) => any): this { + public once(event: string, cb: Handler): this { this._storage.set(event, [cb, true]) return this } @@ -31,24 +30,17 @@ export class Emitter { return this._storage.has(event) } - public async emit(event: string, ...args: any[]): Promise { - return new Promise((res, rej) => { - if (!this._storage.has(event)) { - rej(RPCErrors.EVENT_NOT_REGISTERED) - } + public async emit(event: string, ...args: unknown[]): Promise { + const entry = this._storage.get(event) + if (!entry) { + throw new Error(RPCErrors.EVENT_NOT_REGISTERED) + } - const [cb, once] = this._storage.get(event) as [ - (...args: any[]) => any, - boolean, - ] + const [cb, once] = entry + if (once) { + this._storage.delete(event) + } - if (once) { - this._storage.delete(event) - } - - Promise.resolve(cb(...args)) - .then(res) - .catch(rej) - }) + return (await cb(...(args as never[]))) as R } } From 79faa3b23c62618ed334deae12d9fc4167c73004 Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 22:52:38 +0100 Subject: [PATCH 12/33] feat(rpc): extract _pending* maps to Pending util --- rpc/src/core/client.ts | 36 +++++--------------------- rpc/src/core/server.ts | 22 +++------------- rpc/src/core/webview.ts | 14 ++++++++-- rpc/src/core/wrapper.ts | 13 ++++++++++ rpc/src/utils/pending.ts | 56 ++++++++++++++++++++++++++++++++++++++++ rpc/src/utils/types.ts | 8 ++++++ 6 files changed, 99 insertions(+), 50 deletions(-) create mode 100644 rpc/src/utils/pending.ts diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 12d5a55..dd40f06 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -20,19 +20,13 @@ import { Wrapper } from './wrapper' export class RPCInstanceClient extends Wrapper { private readonly _emitterServer: Emitter - private readonly _pendingServer: Emitter private readonly _emitterWeb: Emitter - private readonly _pendingWeb: Emitter - private readonly _pendingWebToServer: Emitter constructor(props: RPCConfig<'client'>) { super(props) this._emitterServer = new Emitter() - this._pendingServer = new Emitter() this._emitterWeb = new Emitter() - this._pendingWeb = new Emitter() - this._pendingWebToServer = new Emitter() this.console.log('[RPC] Initialized Client') @@ -93,17 +87,8 @@ export class RPCInstanceClient extends Wrapper { } } if (payload.type === 'response') { - if (payload.calledTo === 'client') { - await this._pendingServer.emit( - payload.uuid, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) - } - if (payload.calledTo === 'webview') { - await this._pendingWebToServer.emit( - payload.uuid, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + if (payload.calledTo === 'client' || payload.calledTo === 'webview') { + this.resolvePending(payload) } } } @@ -126,18 +111,13 @@ export class RPCInstanceClient extends Wrapper { payload.player = GetPlayerServerId(PlayerId()) emitNet(RPCEvents.LISTENER_WEB, stringify(payload)) - return new Promise(res => { - this._pendingWebToServer.once(payload.uuid, res) - }) + return this._pending.wait(payload.uuid) } } if (payload.type === 'response') { if (payload.calledTo === 'client') { - await this._pendingWeb.emit( - payload.uuid, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + this.resolvePending(payload) return { status: 'ok' } } @@ -202,9 +182,7 @@ export class RPCInstanceClient extends Wrapper { emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload)) - return new Promise>(res => { - this._pendingServer.once(payload.uuid, res) - }) + return this._pending.wait>(payload.uuid) } // ===== WEBVIEW ===== @@ -261,9 +239,7 @@ export class RPCInstanceClient extends Wrapper { data: payload, }) - return new Promise>(res => { - this._pendingWeb.once(payload.uuid, res) - }) + return this._pending.wait>(payload.uuid) } // ===== SELF ===== diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 8cb72fc..0340a60 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -15,17 +15,13 @@ import { Wrapper } from './wrapper' export class RPCInstanceServer extends Wrapper { private readonly _emitterClient: Emitter - private readonly _pendingClient: Emitter private readonly _emitterWeb: Emitter - private readonly _pendingWeb: Emitter constructor(props: RPCConfig<'server'>) { super(props) this._emitterClient = new Emitter() - this._pendingClient = new Emitter() this._emitterWeb = new Emitter() - this._pendingWeb = new Emitter() this.console.log('[RPC] Initialized Server') @@ -78,10 +74,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) } if (payload.type === 'response') { - await this._pendingClient.emit( - payload.uuid, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + this.resolvePending(payload) } } } @@ -129,10 +122,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) } if (payload.type === 'response') { - await this._pendingWeb.emit( - payload.uuid, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + this.resolvePending(payload) } } } @@ -193,9 +183,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return new Promise>(res => { - this._pendingClient.once(payload.uuid, res) - }) + return this._pending.wait>(payload.uuid) } public async emitClientEveryone< @@ -272,9 +260,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return new Promise>(res => { - this._pendingWeb.once(payload.uuid, res) - }) + return this._pending.wait>(payload.uuid) } // ===== SELF ===== diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 4f0a1d5..d269ef4 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -144,7 +144,7 @@ export class RPCInstanceWebview extends Wrapper { type: 'event', } - return await this._createHttpClientRequest>(payload) + return this._request>(payload) } // ===== SERVER ===== @@ -196,7 +196,7 @@ export class RPCInstanceWebview extends Wrapper { type: 'event', } - return await this._createHttpClientRequest>(payload) + return this._request>(payload) } // ===== SELF ===== @@ -264,6 +264,16 @@ export class RPCInstanceWebview extends Wrapper { // ===== UTILS ===== + /** Sends an event to the client and waits for its response (with timeout) */ + private _request(payload: RPCState): Promise { + const response = this._pending.wait(payload.uuid) + this._createHttpClientRequest(payload).then( + data => this._pending.resolve(payload.uuid, data), + (error: Error) => this._pending.reject(payload.uuid, error), + ) + return response + } + private async _createHttpClientRequest( data: RPCStateRaw | RPCState, ): Promise { diff --git a/rpc/src/core/wrapper.ts b/rpc/src/core/wrapper.ts index 86f3061..a36d4f6 100644 --- a/rpc/src/core/wrapper.ts +++ b/rpc/src/core/wrapper.ts @@ -1,5 +1,6 @@ import { Emitter } from '../utils/emitter' import { parse } from '../utils/funcs' +import { Pending } from '../utils/pending' import { type RPCConfig, type RPCEnvironment, @@ -11,16 +12,28 @@ import { export class Wrapper { protected env: RPCEnvironment protected _emitterLocal: Emitter + protected _pending: Pending protected debug: boolean protected console: Console constructor(cfg: RPCConfig) { this.env = cfg.env this._emitterLocal = new Emitter() + this._pending = new Pending(cfg.timeout ?? 5000) this.debug = cfg.debug ?? false this.console = console } + /** Settles the call waiting for this response; ignores late or unexpected ones */ + protected resolvePending(payload: RPCState): void { + const found = this._pending.resolve(payload.uuid, payload.data?.[0]) + if (!found && this.debug) { + this.console.log( + `[RPC]:ignored response ${payload.event} ${payload.uuid} (no pending call, possibly timed out)`, + ) + } + } + protected verifyEvent(state: Emitter, data: RPCStateRaw | RPCState) { const rpcData = typeof data === 'string' ? parse(data) : data diff --git a/rpc/src/utils/pending.ts b/rpc/src/utils/pending.ts new file mode 100644 index 0000000..d1af6f5 --- /dev/null +++ b/rpc/src/utils/pending.ts @@ -0,0 +1,56 @@ +import { RPCErrors } from './types' + +type Call = { + resolve: (data: unknown) => void + reject: (error: Error) => void + timer: ReturnType | undefined +} + +/** Calls waiting for a response, keyed by payload uuid. */ +export class Pending { + private _calls = new Map() + + /** @param timeout - ms before a call rejects, `0` or less disables it */ + constructor(private readonly _timeout: number) {} + + public wait(uuid: string): Promise { + return new Promise((resolve, reject) => { + const timer = + this._timeout > 0 + ? setTimeout( + () => this.reject(uuid, new Error(RPCErrors.TIMEOUT)), + this._timeout, + ) + : undefined + + this._calls.set(uuid, { + resolve: resolve as (data: unknown) => void, + reject, + timer, + }) + }) + } + + /** @returns `false` if no call waits for this uuid (late or unexpected response) */ + public resolve(uuid: string, data: unknown): boolean { + const call = this._take(uuid) + call?.resolve(data) + return call !== undefined + } + + /** @returns `false` if no call waits for this uuid */ + public reject(uuid: string, error: Error): boolean { + const call = this._take(uuid) + call?.reject(error) + return call !== undefined + } + + private _take(uuid: string): Call | undefined { + const call = this._calls.get(uuid) + if (call) { + this._calls.delete(uuid) + clearTimeout(call.timer) + } + return call + } +} diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index e08ad43..23a23b0 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -24,6 +24,13 @@ export type RPCEnvironmentResolved = export type RPCConfig = { env: T debug?: boolean + /** + * Milliseconds to wait for a response before the call rejects with + * `RPCErrors.TIMEOUT`. `0` disables the timeout. + * + * @defaultValue 5000 + */ + timeout?: number } /** **Internal** */ @@ -85,6 +92,7 @@ export enum RPCErrors { NO_PLAYER = 'No player (failed to resolve from local index)', UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', + TIMEOUT = 'Timed out waiting for response', } /** From 571d1133864bb7362bcef441bc5e88098c6476db Mon Sep 17 00:00:00 2001 From: Danya H Date: Sun, 27 Sep 2026 23:52:39 +0100 Subject: [PATCH 13/33] fix(rpc): better error handling --- rpc/src/core/client.ts | 57 ++++++++---------- rpc/src/core/server.ts | 71 ++++++++++------------- rpc/src/core/webview.ts | 46 +++------------ rpc/src/core/wrapper.ts | 122 ++++++++++++++++++++++++++++++--------- rpc/src/index.ts | 7 ++- rpc/src/utils/errors.ts | 90 +++++++++++++++++++++++++++++ rpc/src/utils/pending.ts | 28 +++++++-- rpc/src/utils/types.ts | 9 ++- 8 files changed, 282 insertions(+), 148 deletions(-) create mode 100644 rpc/src/utils/errors.ts diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index dd40f06..5dfc835 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -1,6 +1,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' +import { RPCError } from '../utils/errors' import { generateUUID, parse, stringify, stringifyWeb } from '../utils/funcs' import { NATIVE_CLIENT_EVENTS, @@ -35,8 +36,11 @@ export class RPCInstanceClient extends Wrapper { on( `__cfx_nui:${RPCEvents.LISTENER_WEB}`, async (data: RPCState, callback: (res: unknown) => void) => { - const res = await this._handleWeb(data) - callback(res) + try { + callback(await this._handleWeb(data)) + } catch (e) { + callback(this.errorResponse(data, e)) + } }, ) } @@ -47,7 +51,7 @@ export class RPCInstanceClient extends Wrapper { try { parse(payloadRaw) } catch { - throw new Error(RPCErrors.INVALID_DATA) + throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) @@ -59,23 +63,7 @@ export class RPCInstanceClient extends Wrapper { if (payload.type === 'event') { if (payload.calledTo === 'client') { - this.verifyEvent(this._emitterServer, payload) - - const responseData = await this._emitterServer.emit( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) - - const response: RPCState = { - event: payload.event, - uuid: payload.uuid, - calledFrom: 'client', - calledTo: 'server', - error: null, - data: [responseData], - player: payload.player, - type: 'response', - } + const response = await this.dispatch(this._emitterServer, payload) emitNet(RPCEvents.LISTENER_CLIENT, stringify(response)) } @@ -87,8 +75,14 @@ export class RPCInstanceClient extends Wrapper { } } if (payload.type === 'response') { - if (payload.calledTo === 'client' || payload.calledTo === 'webview') { - this.resolvePending(payload) + if (payload.calledTo === 'client') { + this.settle(payload) + } + if (payload.calledTo === 'webview') { + // relayed webview -> server call: the webview gets the whole response + if (!this._pending.resolve(payload.uuid, payload)) { + this.logIgnored(payload) + } } } } @@ -102,22 +96,19 @@ export class RPCInstanceClient extends Wrapper { if (payload.type === 'event') { if (payload.calledTo === 'client') { - return await this._emitterWeb.emit( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + return this.dispatch(this._emitterWeb, payload) } if (payload.calledTo === 'server') { payload.player = GetPlayerServerId(PlayerId()) emitNet(RPCEvents.LISTENER_WEB, stringify(payload)) - return this._pending.wait(payload.uuid) + return this._pending.wait(payload) } } if (payload.type === 'response') { if (payload.calledTo === 'client') { - this.resolvePending(payload) + this.settle(payload) return { status: 'ok' } } @@ -182,7 +173,7 @@ export class RPCInstanceClient extends Wrapper { emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload)) - return this._pending.wait>(payload.uuid) + return this._pending.wait>(payload) } // ===== WEBVIEW ===== @@ -239,7 +230,7 @@ export class RPCInstanceClient extends Wrapper { data: payload, }) - return this._pending.wait>(payload.uuid) + return this._pending.wait>(payload) } // ===== SELF ===== @@ -297,7 +288,7 @@ export class RPCInstanceClient extends Wrapper { ) } - this.verifyEvent(this._emitterLocal, payload) + this.assertListener(this._emitterLocal, payload.event) return await this._emitterLocal.emit>( payload.event, @@ -328,7 +319,7 @@ export class RPCInstanceClient extends Wrapper { CallbackArguments extends Parameters, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_CLIENT_EVENTS.includes(eventName)) { - throw new Error(RPCErrors.UNKNOWN_NATIVE) + throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } if (this.debug) { @@ -347,7 +338,7 @@ export class RPCInstanceClient extends Wrapper { >, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) { - throw new Error(RPCErrors.UNKNOWN_NATIVE) + throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } if (this.debug) { diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 0340a60..8a55d2d 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -1,6 +1,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' +import { RPCError } from '../utils/errors' import { generateUUID, parse, stringify } from '../utils/funcs' import { NATIVE_SERVER_EVENTS } from '../utils/native' import { @@ -35,7 +36,7 @@ export class RPCInstanceServer extends Wrapper { try { parse(payloadRaw) } catch { - throw new Error(RPCErrors.INVALID_DATA) + throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) @@ -47,34 +48,27 @@ export class RPCInstanceServer extends Wrapper { if (payload.calledFrom === 'client') { if (payload.type === 'event') { - this.verifyEvent(this._emitterClient, payload) if (payload.player === null || payload.player === -1) { - payload.error = RPCErrors.NO_PLAYER - this.triggerError(payload) + // nobody to reply to, the caller times out + this.console.error( + new RPCError( + RPCErrors.NO_PLAYER, + `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, + ), + ) return } - const responseData = await this._emitterClient.emit( - payload.event, + const response = await this.dispatch( + this._emitterClient, + payload, payload.player, - ...(payload.data && payload.data.length > 0 ? payload.data : []), ) - const response: RPCState = { - event: payload.event, - uuid: payload.uuid, - calledFrom: 'server', - calledTo: 'client', - error: null, - data: [responseData], - player: payload.player, - type: 'response', - } - emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) } if (payload.type === 'response') { - this.resolvePending(payload) + this.settle(payload) } } } @@ -83,7 +77,7 @@ export class RPCInstanceServer extends Wrapper { try { parse(payloadRaw) } catch { - throw new Error(RPCErrors.INVALID_DATA) + throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) } const payload = parse(payloadRaw) @@ -95,34 +89,27 @@ export class RPCInstanceServer extends Wrapper { if (payload.calledFrom === 'webview') { if (payload.type === 'event') { - this.verifyEvent(this._emitterWeb, payload) if (payload.player === null || payload.player === -1) { - payload.error = RPCErrors.NO_PLAYER - this.triggerError(payload) + // nobody to reply to, the caller times out + this.console.error( + new RPCError( + RPCErrors.NO_PLAYER, + `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, + ), + ) return } - const responseData = await this._emitterWeb.emit( - payload.event, + const response = await this.dispatch( + this._emitterWeb, + payload, payload.player, - ...(payload.data && payload.data.length > 0 ? payload.data : []), ) - const response: RPCState = { - event: payload.event, - uuid: payload.uuid, - calledFrom: 'server', - calledTo: 'webview', - error: null, - data: [responseData], - player: payload.player, - type: 'response', - } - emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) } if (payload.type === 'response') { - this.resolvePending(payload) + this.settle(payload) } } } @@ -183,7 +170,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload.uuid) + return this._pending.wait>(payload) } public async emitClientEveryone< @@ -260,7 +247,7 @@ export class RPCInstanceServer extends Wrapper { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload.uuid) + return this._pending.wait>(payload) } // ===== SELF ===== @@ -318,7 +305,7 @@ export class RPCInstanceServer extends Wrapper { ) } - this.verifyEvent(this._emitterLocal, payload) + this.assertListener(this._emitterLocal, payload.event) return await this._emitterLocal.emit>( payload.event, @@ -350,7 +337,7 @@ export class RPCInstanceServer extends Wrapper { CallbackArguments extends Parameters, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_SERVER_EVENTS.includes(eventName)) { - throw new Error(RPCErrors.UNKNOWN_NATIVE) + throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } if (this.debug) { diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index d269ef4..4de4616 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -43,25 +43,9 @@ export class RPCInstanceWebview extends Wrapper { } if (payload.calledFrom === 'client' && payload.type === 'event') { - this.verifyEvent(this._emitterClient, payload) + const response = await this.dispatch(this._emitterClient, payload) - const responseData = await this._emitterClient.emit( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) - - const response: RPCState = { - event: payload.event, - uuid: payload.uuid, - calledFrom: 'webview', - calledTo: 'client', - error: null, - data: [responseData], - player: payload.player, - type: 'response', - } - - await this._createHttpClientRequest(response).then() + await this._createHttpClientRequest(response) } } @@ -73,23 +57,7 @@ export class RPCInstanceWebview extends Wrapper { } if (payload.calledFrom === 'server' && payload.type === 'event') { - this.verifyEvent(this._emitterServer, payload) - - const responseData = await this._emitterServer.emit( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) - - const response: RPCState = { - event: payload.event, - uuid: payload.uuid, - calledFrom: 'webview', - calledTo: 'server', - error: null, - data: [responseData], - player: payload.player, - type: 'response', - } + const response = await this.dispatch(this._emitterServer, payload) await this._createHttpClientRequest(response) } @@ -254,7 +222,7 @@ export class RPCInstanceWebview extends Wrapper { ) } - this.verifyEvent(this._emitterLocal, payload) + this.assertListener(this._emitterLocal, payload.event) return await this._emitterLocal.emit>( payload.event, @@ -266,9 +234,9 @@ export class RPCInstanceWebview extends Wrapper { /** Sends an event to the client and waits for its response (with timeout) */ private _request(payload: RPCState): Promise { - const response = this._pending.wait(payload.uuid) - this._createHttpClientRequest(payload).then( - data => this._pending.resolve(payload.uuid, data), + const response = this._pending.wait(payload) + this._createHttpClientRequest(payload).then( + res => this.settle(res), (error: Error) => this._pending.reject(payload.uuid, error), ) return response diff --git a/rpc/src/core/wrapper.ts b/rpc/src/core/wrapper.ts index a36d4f6..c964e6e 100644 --- a/rpc/src/core/wrapper.ts +++ b/rpc/src/core/wrapper.ts @@ -1,12 +1,15 @@ import { Emitter } from '../utils/emitter' -import { parse } from '../utils/funcs' +import { + handlerErrorMessage, + notRegisteredMessage, + RPCError, +} from '../utils/errors' import { Pending } from '../utils/pending' import { type RPCConfig, type RPCEnvironment, RPCErrors, type RPCState, - type RPCStateRaw, } from '../utils/types' export class Wrapper { @@ -24,41 +27,104 @@ export class Wrapper { this.console = console } - /** Settles the call waiting for this response; ignores late or unexpected ones */ - protected resolvePending(payload: RPCState): void { - const found = this._pending.resolve(payload.uuid, payload.data?.[0]) - if (!found && this.debug) { - this.console.log( - `[RPC]:ignored response ${payload.event} ${payload.uuid} (no pending call, possibly timed out)`, + /** + * Runs the listener for `request` and builds the response to send back. + * Never throws: a missing listener or a thrown error ends up in `error`. + * + * @param prefix - arguments passed before the request data (server: player) + */ + protected async dispatch( + emitter: Emitter, + request: RPCState, + ...prefix: unknown[] + ): Promise { + if (!emitter.has(request.event)) { + return this.errorResponse( + request, + new RPCError( + RPCErrors.EVENT_NOT_REGISTERED, + notRegisteredMessage(request.event, this.env, request.calledFrom), + ), + ) + } + + try { + const data = await emitter.emit( + request.event, + ...prefix, + ...(request.data ?? []), + ) + return this.response(request, [data], null) + } catch (e) { + // keep the stack visible where the listener lives + this.console.error(e) + return this.errorResponse( + request, + new RPCError( + RPCErrors.HANDLER_ERROR, + handlerErrorMessage(request.event, this.env, e), + ), ) } } - protected verifyEvent(state: Emitter, data: RPCStateRaw | RPCState) { - const rpcData = typeof data === 'string' ? parse(data) : data + /** Builds the response to `request` carrying `error` */ + protected errorResponse(request: RPCState, error: unknown): RPCState { + const rpcError = + error instanceof RPCError + ? error + : new RPCError( + RPCErrors.HANDLER_ERROR, + handlerErrorMessage(request.event, this.env, error), + ) + return this.response(request, null, { + code: rpcError.code, + message: rpcError.message, + }) + } - if (!state.has(rpcData.event)) { - rpcData.error = RPCErrors.EVENT_NOT_REGISTERED - this.triggerError(rpcData) + /** Settles the call waiting for `response`; ignores late or unexpected ones */ + protected settle(response: RPCState): void { + const found = response.error + ? this._pending.reject(response.uuid, RPCError.fromResponse(response)) + : this._pending.resolve(response.uuid, response.data?.[0]) + + if (!found) this.logIgnored(response) + } + + /** Throws if no local listener is registered for `event` */ + protected assertListener(emitter: Emitter, event: string): void { + if (!emitter.has(event)) { + throw new RPCError( + RPCErrors.EVENT_NOT_REGISTERED, + notRegisteredMessage(event, this.env, this.env), + { event, uuid: '', from: this.env, to: this.env }, + ) } } - protected triggerError(rpcData: RPCState, error?: string): Error { - const errorMessage = [ - `${rpcData.error}`, - `Event: ${rpcData.event}`, - `Uuid: ${rpcData.uuid}`, - `From: ${rpcData.calledFrom}`, - `To: ${rpcData.calledTo}`, - `Player: ${rpcData.player}`, - `Type: ${rpcData.type}`, - `Data: ${rpcData.data}`, - ] - - if (error) { - errorMessage.push(`Info: ${error}`) + protected logIgnored(response: RPCState): void { + if (this.debug) { + this.console.log( + `[RPC]:ignored response ${response.event} ${response.uuid} (no pending call, possibly timed out)`, + ) } + } - throw new Error(errorMessage.join('\n | ')) + private response( + request: RPCState, + data: RPCState['data'], + error: RPCState['error'], + ): RPCState { + return { + event: request.event, + uuid: request.uuid, + calledFrom: this.env, + calledTo: request.calledFrom, + error, + data, + player: request.player, + type: 'response', + } } } diff --git a/rpc/src/index.ts b/rpc/src/index.ts index a710e54..e67b16f 100644 --- a/rpc/src/index.ts +++ b/rpc/src/index.ts @@ -2,6 +2,7 @@ import { RPCInstanceClient } from './core/client' import { RPCInstanceServer } from './core/server' import { RPCInstanceWebview } from './core/webview' import { Wrapper } from './core/wrapper' +import { RPCError } from './utils/errors' import { type RPCConfig, type RPCEnvironment, @@ -54,7 +55,10 @@ class RPCFactory extends Wrapper { this.operator = new RPCInstanceWebview(opts as RPCConfig<'webview'>) break default: - throw new Error(RPCErrors.UNKNOWN_ENVIRONMENT) + throw new RPCError( + RPCErrors.UNKNOWN_ENVIRONMENT, + RPCErrors.UNKNOWN_ENVIRONMENT, + ) } } @@ -65,6 +69,7 @@ class RPCFactory extends Wrapper { export { RPCFactory } export * from './utils/types' +export { RPCError, type RPCErrorDetails } from './utils/errors' export * from './utils/native' export type * from './core/server' export type * from './core/client' diff --git a/rpc/src/utils/errors.ts b/rpc/src/utils/errors.ts new file mode 100644 index 0000000..193905c --- /dev/null +++ b/rpc/src/utils/errors.ts @@ -0,0 +1,90 @@ +import { type RPCEnvironment, RPCErrors, type RPCState } from './types' + +/** Where a failed call was going, when the error comes from a call */ +export type RPCErrorDetails = { + event: string + uuid: string + /** Environment that made the call */ + from: RPCEnvironment + /** Environment that was called */ + to: RPCEnvironment +} + +/** + * Error thrown or rejected by this library. Check `code` against `RPCErrors`. + * + * @example + * try { + * await rpc.emitServer('buyItem', 'water') + * } catch (e) { + * if (e instanceof RPCError && e.code === RPCErrors.TIMEOUT) { + * // server did not answer in time + * } + * } + */ +export class RPCError extends Error { + public readonly code: RPCErrors + public readonly details: RPCErrorDetails | undefined + + constructor(code: RPCErrors, message: string, details?: RPCErrorDetails) { + super(message) + this.name = 'RPCError' + this.code = code + this.details = details + } + + /** **Internal** Rebuilds the error a receiver sent back in a response */ + static fromResponse(response: RPCState): RPCError { + return new RPCError( + response.error?.code ?? RPCErrors.HANDLER_ERROR, + response.error?.message ?? 'Unknown error', + { + event: response.event, + uuid: response.uuid, + from: response.calledTo, + to: response.calledFrom, + }, + ) + } +} + +/** **Internal** Name of the method that listens on `receiver` for calls from `sender` */ +export function listenerName( + receiver: RPCEnvironment, + sender: RPCEnvironment, +): string { + if (receiver === sender) return 'onSelf' + return `on${sender[0]?.toUpperCase()}${sender.slice(1)}` +} + +/** **Internal** */ +export function notRegisteredMessage( + event: string, + receiver: RPCEnvironment, + sender: RPCEnvironment, +): string { + return `No listener for "${event}" on ${receiver}. Register it with rpc.${listenerName(receiver, sender)}("${event}", ...) in ${receiver} code.` +} + +/** **Internal** */ +export function handlerErrorMessage( + event: string, + receiver: RPCEnvironment, + cause: unknown, +): string { + const reason = + cause instanceof Error || (cause as Error | undefined)?.message + ? (cause as Error).message + : String(cause) + return `Listener for "${event}" on ${receiver} threw: ${reason}` +} + +/** **Internal** */ +export function timeoutMessage( + event: string, + receiver: RPCEnvironment, + sender: RPCEnvironment, + timeout: number, +): string { + return `No response for "${event}" from ${receiver} within ${timeout} ms. Check that ${receiver} registered rpc.${listenerName(receiver, sender)}("${event}", ...) and that it returns, or raise RPCConfig.timeout (0 disables it).` +} diff --git a/rpc/src/utils/pending.ts b/rpc/src/utils/pending.ts index d1af6f5..07dd1ad 100644 --- a/rpc/src/utils/pending.ts +++ b/rpc/src/utils/pending.ts @@ -1,4 +1,5 @@ -import { RPCErrors } from './types' +import { RPCError, timeoutMessage } from './errors' +import { RPCErrors, type RPCState } from './types' type Call = { resolve: (data: unknown) => void @@ -13,17 +14,18 @@ export class Pending { /** @param timeout - ms before a call rejects, `0` or less disables it */ constructor(private readonly _timeout: number) {} - public wait(uuid: string): Promise { + /** Waits for the response to `request`, rejects with `RPCErrors.TIMEOUT` */ + public wait(request: RPCState): Promise { return new Promise((resolve, reject) => { const timer = this._timeout > 0 ? setTimeout( - () => this.reject(uuid, new Error(RPCErrors.TIMEOUT)), + () => this.reject(request.uuid, this._timeoutError(request)), this._timeout, ) : undefined - this._calls.set(uuid, { + this._calls.set(request.uuid, { resolve: resolve as (data: unknown) => void, reject, timer, @@ -53,4 +55,22 @@ export class Pending { } return call } + + private _timeoutError(request: RPCState): RPCError { + return new RPCError( + RPCErrors.TIMEOUT, + timeoutMessage( + request.event, + request.calledTo, + request.calledFrom, + this._timeout, + ), + { + event: request.event, + uuid: request.uuid, + from: request.calledFrom, + to: request.calledTo, + }, + ) + } } diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index 23a23b0..3e793df 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -46,12 +46,18 @@ export type RPCState = { uuid: string calledFrom: RPCEnvironment calledTo: RPCEnvironment - error: string | null + error: RPCErrorPayload | null data: unknown[] | null player: number | null type: RPCEventType } +/** **Internal** Error sent back in a response, rebuilt as `RPCError` by the caller */ +export type RPCErrorPayload = { + code: RPCErrors + message: string +} + /** * **Internal** * @@ -93,6 +99,7 @@ export enum RPCErrors { UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', TIMEOUT = 'Timed out waiting for response', + HANDLER_ERROR = 'Listener threw an error', } /** From 558f04567a43563e590683e76fdde9e7c4f0c76e Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:00:15 +0100 Subject: [PATCH 14/33] chore(rpc): promote Wrapper to BaseInstance --- rpc/src/core/{wrapper.ts => base.ts} | 115 +++++++++++++++------- rpc/src/core/client.ts | 139 ++++++-------------------- rpc/src/core/server.ts | 142 +++++---------------------- rpc/src/core/webview.ts | 118 ++++------------------ rpc/src/index.ts | 7 +- rpc/src/utils/emitter.ts | 2 +- 6 files changed, 156 insertions(+), 367 deletions(-) rename rpc/src/core/{wrapper.ts => base.ts} (60%) diff --git a/rpc/src/core/wrapper.ts b/rpc/src/core/base.ts similarity index 60% rename from rpc/src/core/wrapper.ts rename to rpc/src/core/base.ts index c964e6e..5bd4dc6 100644 --- a/rpc/src/core/wrapper.ts +++ b/rpc/src/core/base.ts @@ -1,9 +1,10 @@ -import { Emitter } from '../utils/emitter' +import { Emitter, type Handler } from '../utils/emitter' import { handlerErrorMessage, notRegisteredMessage, RPCError, } from '../utils/errors' +import { generateUUID } from '../utils/funcs' import { Pending } from '../utils/pending' import { type RPCConfig, @@ -12,21 +13,84 @@ import { type RPCState, } from '../utils/types' -export class Wrapper { - protected env: RPCEnvironment - protected _emitterLocal: Emitter - protected _pending: Pending - protected debug: boolean - protected console: Console +/** Shared plumbing of the server, client and webview instances */ +export class RPCInstanceBase { + protected readonly env: RPCEnvironment + protected readonly debug: boolean + protected readonly _emitterLocal = new Emitter() + protected readonly _pending: Pending constructor(cfg: RPCConfig) { this.env = cfg.env - this._emitterLocal = new Emitter() - this._pending = new Pending(cfg.timeout ?? 5000) this.debug = cfg.debug ?? false - this.console = console + this._pending = new Pending(cfg.timeout ?? 5000) } + // ===== LISTENERS ===== + + /** Registers `cb` for `event` on `emitter`; `method` is only used for logs */ + protected listen( + emitter: Emitter, + method: string, + event: string, + cb: Handler, + ): this { + this.log(`${method} ${event}`) + emitter.on(event, cb) + return this + } + + protected unlisten(emitter: Emitter, method: string, event: string): this { + this.log(`${method} ${event}`) + emitter.off(event) + return this + } + + // ===== OUTGOING ===== + + /** Builds an event payload sent from this environment to `to` */ + protected request( + event: string, + to: RPCEnvironment, + args: unknown[], + player: number | null, + ): RPCState { + return { + event, + uuid: generateUUID(), + calledFrom: this.env, + calledTo: to, + error: null, + data: args, + player, + type: 'event', + } + } + + /** Calls a listener registered with `onSelf` in this environment */ + protected emitLocal(event: string, args: unknown[]): Promise { + this.log(`emitSelf ${event}`) + if (!this._emitterLocal.has(event)) { + throw new RPCError( + RPCErrors.EVENT_NOT_REGISTERED, + notRegisteredMessage(event, this.env, this.env), + { event, uuid: '', from: this.env, to: this.env }, + ) + } + return this._emitterLocal.emit(event, ...args) + } + + /** Settles the call waiting for `response`; ignores late or unexpected ones */ + protected settle(response: RPCState): void { + const found = response.error + ? this._pending.reject(response.uuid, RPCError.fromResponse(response)) + : this._pending.resolve(response.uuid, response.data?.[0]) + + if (!found) this.logIgnored(response) + } + + // ===== INCOMING ===== + /** * Runs the listener for `request` and builds the response to send back. * Never throws: a missing listener or a thrown error ends up in `error`. @@ -57,7 +121,7 @@ export class Wrapper { return this.response(request, [data], null) } catch (e) { // keep the stack visible where the listener lives - this.console.error(e) + console.error(e) return this.errorResponse( request, new RPCError( @@ -83,32 +147,17 @@ export class Wrapper { }) } - /** Settles the call waiting for `response`; ignores late or unexpected ones */ - protected settle(response: RPCState): void { - const found = response.error - ? this._pending.reject(response.uuid, RPCError.fromResponse(response)) - : this._pending.resolve(response.uuid, response.data?.[0]) + // ===== LOGS ===== - if (!found) this.logIgnored(response) - } - - /** Throws if no local listener is registered for `event` */ - protected assertListener(emitter: Emitter, event: string): void { - if (!emitter.has(event)) { - throw new RPCError( - RPCErrors.EVENT_NOT_REGISTERED, - notRegisteredMessage(event, this.env, this.env), - { event, uuid: '', from: this.env, to: this.env }, - ) - } + /** Debug-only log, prefixed with the environment */ + protected log(message: string): void { + if (this.debug) console.log(`[RPC]:${this.env}:${message}`) } protected logIgnored(response: RPCState): void { - if (this.debug) { - this.console.log( - `[RPC]:ignored response ${response.event} ${response.uuid} (no pending call, possibly timed out)`, - ) - } + this.log( + `ignored response ${response.event} ${response.uuid} (no pending call, possibly timed out)`, + ) } private response( diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 5dfc835..fde135b 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -2,7 +2,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' import { RPCError } from '../utils/errors' -import { generateUUID, parse, stringify, stringifyWeb } from '../utils/funcs' +import { parse, stringify, stringifyWeb } from '../utils/funcs' import { NATIVE_CLIENT_EVENTS, NATIVE_CLIENT_NETWORK_EVENTS, @@ -17,9 +17,9 @@ import { type RPCStateRaw, type RPCStateWeb, } from '../utils/types' -import { Wrapper } from './wrapper' +import { RPCInstanceBase } from './base' -export class RPCInstanceClient extends Wrapper { +export class RPCInstanceClient extends RPCInstanceBase { private readonly _emitterServer: Emitter private readonly _emitterWeb: Emitter @@ -29,7 +29,7 @@ export class RPCInstanceClient extends Wrapper { this._emitterServer = new Emitter() this._emitterWeb = new Emitter() - this.console.log('[RPC] Initialized Client') + console.log('[RPC] Initialized Client') onNet(RPCEvents.LISTENER_SERVER, this._handleServer.bind(this)) RegisterNuiCallbackType(RPCEvents.LISTENER_WEB) @@ -55,11 +55,9 @@ export class RPCInstanceClient extends Wrapper { } const payload = parse(payloadRaw) - if (this.debug) { - this.console.log( - `[RPC]:client:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.type === 'event') { if (payload.calledTo === 'client') { @@ -88,11 +86,9 @@ export class RPCInstanceClient extends Wrapper { } private async _handleWeb(payload: RPCState): Promise { - if (this.debug) { - this.console.log( - `[RPC]:client:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.type === 'event') { if (payload.calledTo === 'client') { @@ -134,25 +130,13 @@ export class RPCInstanceClient extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onServer ${eventName}`) - } - - this._emitterServer.on(eventName, cb) - - return this + return this.listen(this._emitterServer, 'onServer', eventName, cb) } public offServer( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offServer ${eventName}`) - } - - this._emitterServer.off(eventName) - - return this + return this.unlisten(this._emitterServer, 'offServer', eventName) } public async emitServer< @@ -160,16 +144,12 @@ export class RPCInstanceClient extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'client', - calledTo: 'server', - error: null, - data: args.length ? args : null, - player: GetPlayerServerId(PlayerId()), - type: 'event', - } + const payload = this.request( + eventName, + 'server', + args, + GetPlayerServerId(PlayerId()), + ) emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload)) @@ -188,25 +168,13 @@ export class RPCInstanceClient extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onWebview ${eventName}`) - } - - this._emitterWeb.on(eventName, cb) - - return this + return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } public offWebview( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offWebview ${eventName}`) - } - - this._emitterWeb.off(eventName) - - return this + return this.unlisten(this._emitterWeb, 'offWebview', eventName) } public async emitWebview< @@ -214,16 +182,7 @@ export class RPCInstanceClient extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'client', - calledTo: 'webview', - error: null, - data: args.length ? args : null, - player: PlayerId(), - type: 'event', - } + const payload = this.request(eventName, 'webview', args, PlayerId()) this._sendWebMessage({ origin: RPCEvents.LISTENER_CLIENT, @@ -245,25 +204,13 @@ export class RPCInstanceClient extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onSelf ${eventName}`) - } - - this._emitterLocal.on(eventName, cb) - - return this + return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } public offSelf( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offSelf ${eventName}`) - } - - this._emitterLocal.off(eventName) - - return this + return this.unlisten(this._emitterLocal, 'offSelf', eventName) } public async emitSelf< @@ -271,29 +218,7 @@ export class RPCInstanceClient extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'client', - calledTo: 'client', - error: null, - data: args.length ? args : null, - player: null, - type: 'event', - } - - if (this.debug) { - this.console.log( - `[RPC]:accepted ${payload.event} from ${payload.calledFrom}`, - ) - } - - this.assertListener(this._emitterLocal, payload.event) - - return await this._emitterLocal.emit>( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + return this.emitLocal>(eventName, args) } // ===== OTHER ===== @@ -305,9 +230,7 @@ export class RPCInstanceClient extends Wrapper { command: CommandName, cb: (player: number, args: CallbackArguments, commandRaw: string) => void, ): this { - if (this.debug) { - this.console.log(`[RPC]:onCommand ${command}`) - } + this.log(`onCommand ${command}`) RegisterCommand(command, cb, false) @@ -322,9 +245,7 @@ export class RPCInstanceClient extends Wrapper { throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } - if (this.debug) { - this.console.log(`[RPC]:onNativeEvent ${eventName}`) - } + this.log(`onNativeEvent ${eventName}`) on(eventName, cb) @@ -341,9 +262,7 @@ export class RPCInstanceClient extends Wrapper { throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } - if (this.debug) { - this.console.log(`[RPC]:onNativeNetworkEvent ${eventName}`) - } + this.log(`onNativeNetworkEvent ${eventName}`) on(eventName, cb) @@ -351,9 +270,7 @@ export class RPCInstanceClient extends Wrapper { } public setWebviewFocus(hasFocus: boolean, hasCursor: boolean): this { - if (this.debug) { - this.console.log(`[RPC]:setWebviewFocus ${hasFocus} ${hasCursor}`) - } + this.log(`setWebviewFocus ${hasFocus} ${hasCursor}`) SetNuiFocus(hasFocus, hasCursor) diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 8a55d2d..cb7f103 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -2,19 +2,18 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' import { RPCError } from '../utils/errors' -import { generateUUID, parse, stringify } from '../utils/funcs' +import { parse, stringify } from '../utils/funcs' import { NATIVE_SERVER_EVENTS } from '../utils/native' import { type RPCConfig, RPCErrors, RPCEvents, type RPCNativeServerEvents, - type RPCState, type RPCStateRaw, } from '../utils/types' -import { Wrapper } from './wrapper' +import { RPCInstanceBase } from './base' -export class RPCInstanceServer extends Wrapper { +export class RPCInstanceServer extends RPCInstanceBase { private readonly _emitterClient: Emitter private readonly _emitterWeb: Emitter @@ -24,7 +23,7 @@ export class RPCInstanceServer extends Wrapper { this._emitterClient = new Emitter() this._emitterWeb = new Emitter() - this.console.log('[RPC] Initialized Server') + console.log('[RPC] Initialized Server') onNet(RPCEvents.LISTENER_CLIENT, this._handleClient.bind(this)) onNet(RPCEvents.LISTENER_WEB, this._handleWeb.bind(this)) @@ -40,17 +39,15 @@ export class RPCInstanceServer extends Wrapper { } const payload = parse(payloadRaw) - if (this.debug) { - this.console.log( - `[RPC]:server:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.calledFrom === 'client') { if (payload.type === 'event') { if (payload.player === null || payload.player === -1) { // nobody to reply to, the caller times out - this.console.error( + console.error( new RPCError( RPCErrors.NO_PLAYER, `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, @@ -81,17 +78,15 @@ export class RPCInstanceServer extends Wrapper { } const payload = parse(payloadRaw) - if (this.debug) { - this.console.log( - `[RPC]:server:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.calledFrom === 'webview') { if (payload.type === 'event') { if (payload.player === null || payload.player === -1) { // nobody to reply to, the caller times out - this.console.error( + console.error( new RPCError( RPCErrors.NO_PLAYER, `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, @@ -127,25 +122,13 @@ export class RPCInstanceServer extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onClient ${eventName}`) - } - - this._emitterClient.on(eventName, cb) - - return this + return this.listen(this._emitterClient, 'onClient', eventName, cb) } public offClient( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offClient ${eventName}`) - } - - this._emitterClient.off(eventName) - - return this + return this.unlisten(this._emitterClient, 'offClient', eventName) } public async emitClient< @@ -157,16 +140,7 @@ export class RPCInstanceServer extends Wrapper { eventName: EventName, ...args: Arguments ): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'server', - calledTo: 'client', - error: null, - data: args.length ? args : null, - player: player, - type: 'event', - } + const payload = this.request(eventName, 'client', args, player) emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) @@ -177,16 +151,7 @@ export class RPCInstanceServer extends Wrapper { EventName extends keyof s.RPCEvents_ServerClient, Arguments extends Parameters, >(eventName: EventName, ...args: Arguments): Promise { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'server', - calledTo: 'client', - error: null, - data: args.length ? args : null, - player: -1, - type: 'event', - } + const payload = this.request(eventName, 'client', args, -1) emitNet(RPCEvents.LISTENER_SERVER, -1, stringify(payload)) } @@ -204,25 +169,13 @@ export class RPCInstanceServer extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onWebview ${eventName}`) - } - - this._emitterWeb.on(eventName, cb) - - return this + return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } public offWebview( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offWebview ${eventName}`) - } - - this._emitterWeb.off(eventName) - - return this + return this.unlisten(this._emitterWeb, 'offWebview', eventName) } public async emitWebview< @@ -234,16 +187,7 @@ export class RPCInstanceServer extends Wrapper { eventName: EventName, ...args: Arguments ): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'server', - calledTo: 'webview', - error: null, - data: args.length ? args : null, - player: player, - type: 'event', - } + const payload = this.request(eventName, 'webview', args, player) emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) @@ -262,25 +206,13 @@ export class RPCInstanceServer extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onSelf ${eventName}`) - } - - this._emitterLocal.on(eventName, cb) - - return this + return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } public offSelf( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offSelf ${eventName}`) - } - - this._emitterLocal.off(eventName) - - return this + return this.unlisten(this._emitterLocal, 'offSelf', eventName) } public async emitSelf< @@ -288,29 +220,7 @@ export class RPCInstanceServer extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'server', - calledTo: 'server', - error: null, - data: args.length ? args : null, - player: null, - type: 'event', - } - - if (this.debug) { - this.console.log( - `[RPC]:accepted ${payload.event} from ${payload.calledFrom}`, - ) - } - - this.assertListener(this._emitterLocal, payload.event) - - return await this._emitterLocal.emit>( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + return this.emitLocal>(eventName, args) } // ===== OTHER ===== @@ -323,9 +233,7 @@ export class RPCInstanceServer extends Wrapper { cb: (player: number, args: CallbackArguments, commandRaw: string) => void, restricted = false, ): this { - if (this.debug) { - this.console.log(`[RPC]:onCommand ${command}`) - } + this.log(`onCommand ${command}`) RegisterCommand(command, cb, restricted) @@ -340,9 +248,7 @@ export class RPCInstanceServer extends Wrapper { throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) } - if (this.debug) { - this.console.log(`[RPC]:onNativeEvent ${eventName}`) - } + this.log(`onNativeEvent ${eventName}`) on(eventName, cb) diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 4de4616..b3ff380 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -1,7 +1,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' -import { generateUUID, stringify } from '../utils/funcs' +import { stringify } from '../utils/funcs' import { RPCEvents, type RPCConfig, @@ -9,9 +9,9 @@ import { type RPCStateRaw, type RPCStateWeb, } from '../utils/types' -import { Wrapper } from './wrapper' +import { RPCInstanceBase } from './base' -export class RPCInstanceWebview extends Wrapper { +export class RPCInstanceWebview extends RPCInstanceBase { private readonly _emitterClient: Emitter private readonly _emitterServer: Emitter @@ -21,7 +21,7 @@ export class RPCInstanceWebview extends Wrapper { this._emitterClient = new Emitter() this._emitterServer = new Emitter() - this.console.log('[RPC] Initialized Webview') + console.log('[RPC] Initialized Webview') window.addEventListener('message', (e: MessageEvent) => { if (e.data.origin === RPCEvents.LISTENER_CLIENT) { @@ -36,11 +36,9 @@ export class RPCInstanceWebview extends Wrapper { // ===== HANDLERS ===== private async _handleClient(payload: RPCState) { - if (this.debug) { - this.console.log( - `[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.calledFrom === 'client' && payload.type === 'event') { const response = await this.dispatch(this._emitterClient, payload) @@ -50,11 +48,9 @@ export class RPCInstanceWebview extends Wrapper { } private async _handleServer(payload: RPCState) { - if (this.debug) { - this.console.log( - `[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - } + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) if (payload.calledFrom === 'server' && payload.type === 'event') { const response = await this.dispatch(this._emitterServer, payload) @@ -75,25 +71,13 @@ export class RPCInstanceWebview extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onClient ${eventName}`) - } - - this._emitterClient.on(eventName, cb) - - return this + return this.listen(this._emitterClient, 'onClient', eventName, cb) } public offClient( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offClient ${eventName}`) - } - - this._emitterClient.off(eventName) - - return this + return this.unlisten(this._emitterClient, 'offClient', eventName) } public async emitClient< @@ -101,16 +85,7 @@ export class RPCInstanceWebview extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'webview', - calledTo: 'client', - error: null, - data: args.length ? args : null, - player: null, - type: 'event', - } + const payload = this.request(eventName, 'client', args, null) return this._request>(payload) } @@ -127,25 +102,13 @@ export class RPCInstanceWebview extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onServer ${eventName}`) - } - - this._emitterServer.on(eventName, cb) - - return this + return this.listen(this._emitterServer, 'onServer', eventName, cb) } public offServer( eventName: EventName, ): RPCInstanceWebview { - if (this.debug) { - this.console.log(`[RPC]:offServer ${eventName}`) - } - - this._emitterServer.off(eventName) - - return this + return this.unlisten(this._emitterServer, 'offServer', eventName) } public async emitServer< @@ -153,16 +116,7 @@ export class RPCInstanceWebview extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'webview', - calledTo: 'server', - error: null, - data: args.length ? args : null, - player: null, - type: 'event', - } + const payload = this.request(eventName, 'server', args, null) return this._request>(payload) } @@ -179,25 +133,13 @@ export class RPCInstanceWebview extends Wrapper { ...args: CallbackArguments ) => Awaited | Promise>, ): this { - if (this.debug) { - this.console.log(`[RPC]:onSelf ${eventName}`) - } - - this._emitterLocal.on(eventName, cb) - - return this + return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } public offSelf( eventName: EventName, ): this { - if (this.debug) { - this.console.log(`[RPC]:offSelf ${eventName}`) - } - - this._emitterLocal.off(eventName) - - return this + return this.unlisten(this._emitterLocal, 'offSelf', eventName) } public async emitSelf< @@ -205,29 +147,7 @@ export class RPCInstanceWebview extends Wrapper { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload: RPCState = { - event: eventName, - uuid: generateUUID(), - calledFrom: 'webview', - calledTo: 'webview', - error: null, - data: args.length ? args : null, - player: null, - type: 'event', - } - - if (this.debug) { - this.console.log( - `[RPC]:accepted ${payload.event} from ${payload.calledFrom}`, - ) - } - - this.assertListener(this._emitterLocal, payload.event) - - return await this._emitterLocal.emit>( - payload.event, - ...(payload.data && payload.data.length > 0 ? payload.data : []), - ) + return this.emitLocal>(eventName, args) } // ===== UTILS ===== diff --git a/rpc/src/index.ts b/rpc/src/index.ts index e67b16f..82e7c17 100644 --- a/rpc/src/index.ts +++ b/rpc/src/index.ts @@ -1,7 +1,6 @@ import { RPCInstanceClient } from './core/client' import { RPCInstanceServer } from './core/server' import { RPCInstanceWebview } from './core/webview' -import { Wrapper } from './core/wrapper' import { RPCError } from './utils/errors' import { type RPCConfig, @@ -27,7 +26,7 @@ import { * * @class */ -class RPCFactory extends Wrapper { +class RPCFactory { private readonly operator: | RPCInstanceServer | RPCInstanceClient @@ -40,9 +39,7 @@ class RPCFactory extends Wrapper { * @param {boolean} opts.debug - Show additional logs */ constructor(opts: RPCConfig) { - super(opts) - - this.console.log('[RPC] Initializing...') + console.log('[RPC] Initializing...') switch (opts.env) { case 'server': diff --git a/rpc/src/utils/emitter.ts b/rpc/src/utils/emitter.ts index 76a9882..e3c9623 100644 --- a/rpc/src/utils/emitter.ts +++ b/rpc/src/utils/emitter.ts @@ -4,7 +4,7 @@ import { RPCErrors } from './types' * Accepts any callback. Argument types are enforced by the typed `on*`/`emit*` * methods that wrap the emitter, not here. */ -type Handler = (...args: never[]) => unknown +export type Handler = (...args: never[]) => unknown /** One handler per event: registering an event again replaces its handler. */ export class Emitter { From b48d3f5240ed56d1f7b04f9bcf00f4b4cbe56ebd Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:22:45 +0100 Subject: [PATCH 15/33] fix(rpc): improve handlers --- rpc/src/core/base.ts | 14 +++----------- rpc/src/core/client.ts | 27 +++++++++------------------ rpc/src/core/server.ts | 26 +++++--------------------- rpc/src/core/webview.ts | 40 +++++++++++++++++++++++----------------- rpc/src/utils/funcs.ts | 37 ++++++++++++++++++++++++++++++++++--- rpc/src/utils/types.ts | 1 - 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/rpc/src/core/base.ts b/rpc/src/core/base.ts index 5bd4dc6..3330db8 100644 --- a/rpc/src/core/base.ts +++ b/rpc/src/core/base.ts @@ -13,7 +13,6 @@ import { type RPCState, } from '../utils/types' -/** Shared plumbing of the server, client and webview instances */ export class RPCInstanceBase { protected readonly env: RPCEnvironment protected readonly debug: boolean @@ -26,9 +25,7 @@ export class RPCInstanceBase { this._pending = new Pending(cfg.timeout ?? 5000) } - // ===== LISTENERS ===== - - /** Registers `cb` for `event` on `emitter`; `method` is only used for logs */ + /** Registers `cb` for `event` on `emitter`; `method` is only for logs */ protected listen( emitter: Emitter, method: string, @@ -39,15 +36,14 @@ export class RPCInstanceBase { emitter.on(event, cb) return this } - + + /** Unregisters `cb` for `event` on `emitter`; `method` is only for logs */ protected unlisten(emitter: Emitter, method: string, event: string): this { this.log(`${method} ${event}`) emitter.off(event) return this } - // ===== OUTGOING ===== - /** Builds an event payload sent from this environment to `to` */ protected request( event: string, @@ -89,8 +85,6 @@ export class RPCInstanceBase { if (!found) this.logIgnored(response) } - // ===== INCOMING ===== - /** * Runs the listener for `request` and builds the response to send back. * Never throws: a missing listener or a thrown error ends up in `error`. @@ -147,8 +141,6 @@ export class RPCInstanceBase { }) } - // ===== LOGS ===== - /** Debug-only log, prefixed with the environment */ protected log(message: string): void { if (this.debug) console.log(`[RPC]:${this.env}:${message}`) diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index fde135b..98bdd39 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -2,7 +2,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' import { RPCError } from '../utils/errors' -import { parse, stringify, stringifyWeb } from '../utils/funcs' +import { stringify, stringifyWeb } from '../utils/funcs' import { NATIVE_CLIENT_EVENTS, NATIVE_CLIENT_NETWORK_EVENTS, @@ -35,11 +35,14 @@ export class RPCInstanceClient extends RPCInstanceBase { RegisterNuiCallbackType(RPCEvents.LISTENER_WEB) on( `__cfx_nui:${RPCEvents.LISTENER_WEB}`, - async (data: RPCState, callback: (res: unknown) => void) => { + async (data: unknown, callback: (res: unknown) => void) => { + const payload = this.accept(data) + if (!payload) return callback({ status: 'invalid' }) + try { - callback(await this._handleWeb(data)) + callback(await this._handleWeb(payload)) } catch (e) { - callback(this.errorResponse(data, e)) + callback(this.errorResponse(payload, e)) } }, ) @@ -48,16 +51,8 @@ export class RPCInstanceClient extends RPCInstanceBase { // ===== HANDLERS ===== private async _handleServer(payloadRaw: RPCStateRaw) { - try { - parse(payloadRaw) - } catch { - throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) - } - const payload = parse(payloadRaw) - - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) + const payload = this.accept(payloadRaw) + if (!payload) return if (payload.type === 'event') { if (payload.calledTo === 'client') { @@ -86,10 +81,6 @@ export class RPCInstanceClient extends RPCInstanceBase { } private async _handleWeb(payload: RPCState): Promise { - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - if (payload.type === 'event') { if (payload.calledTo === 'client') { return this.dispatch(this._emitterWeb, payload) diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index cb7f103..b7ea9f3 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -2,7 +2,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' import { RPCError } from '../utils/errors' -import { parse, stringify } from '../utils/funcs' +import { stringify } from '../utils/funcs' import { NATIVE_SERVER_EVENTS } from '../utils/native' import { type RPCConfig, @@ -32,16 +32,8 @@ export class RPCInstanceServer extends RPCInstanceBase { // ===== HANDLERS ===== private async _handleClient(payloadRaw: RPCStateRaw) { - try { - parse(payloadRaw) - } catch { - throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) - } - const payload = parse(payloadRaw) - - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) + const payload = this.accept(payloadRaw) + if (!payload) return if (payload.calledFrom === 'client') { if (payload.type === 'event') { @@ -71,16 +63,8 @@ export class RPCInstanceServer extends RPCInstanceBase { } private async _handleWeb(payloadRaw: RPCStateRaw) { - try { - parse(payloadRaw) - } catch { - throw new RPCError(RPCErrors.INVALID_DATA, RPCErrors.INVALID_DATA) - } - const payload = parse(payloadRaw) - - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) + const payload = this.accept(payloadRaw) + if (!payload) return if (payload.calledFrom === 'webview') { if (payload.type === 'event') { diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index b3ff380..10ebeb2 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -23,23 +23,30 @@ export class RPCInstanceWebview extends RPCInstanceBase { console.log('[RPC] Initialized Webview') - window.addEventListener('message', (e: MessageEvent) => { - if (e.data.origin === RPCEvents.LISTENER_CLIENT) { - this._handleClient(e.data.data) - } - if (e.data.origin === RPCEvents.LISTENER_SERVER) { - this._handleServer(e.data.data) - } - }) + window.addEventListener( + 'message', + (e: MessageEvent | null>) => { + const origin = e.data?.origin + // not ours, e.g. the resource's own SendNUIMessage calls + if ( + origin !== RPCEvents.LISTENER_CLIENT && + origin !== RPCEvents.LISTENER_SERVER + ) { + return + } + + const payload = this.accept(e.data?.data) + if (!payload) return + + if (origin === RPCEvents.LISTENER_CLIENT) this._handleClient(payload) + else this._handleServer(payload) + }, + ) } // ===== HANDLERS ===== private async _handleClient(payload: RPCState) { - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - if (payload.calledFrom === 'client' && payload.type === 'event') { const response = await this.dispatch(this._emitterClient, payload) @@ -48,10 +55,6 @@ export class RPCInstanceWebview extends RPCInstanceBase { } private async _handleServer(payload: RPCState) { - this.log( - `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, - ) - if (payload.calledFrom === 'server' && payload.type === 'event') { const response = await this.dispatch(this._emitterServer, payload) @@ -156,7 +159,10 @@ export class RPCInstanceWebview extends RPCInstanceBase { private _request(payload: RPCState): Promise { const response = this._pending.wait(payload) this._createHttpClientRequest(payload).then( - res => this.settle(res), + res => { + const reply = this.accept(res) + if (reply) this.settle(reply) + }, (error: Error) => this._pending.reject(payload.uuid, error), ) return response diff --git a/rpc/src/utils/funcs.ts b/rpc/src/utils/funcs.ts index 56c81c9..e2425a3 100644 --- a/rpc/src/utils/funcs.ts +++ b/rpc/src/utils/funcs.ts @@ -1,17 +1,48 @@ import type { + RPCEnvironment, RPCState, RPCStateRaw, RPCStateWeb, RPCStateWebRaw, } from './types' +const ENVIRONMENTS: readonly unknown[] = [ + 'server', + 'client', + 'webview', +] satisfies RPCEnvironment[] + /** * **Internal** * - * Typed data parser + * Checks the shape of an incoming payload. Payloads come from the network or + * another runtime, so nothing about them is trusted. */ -export function parse(data: RPCStateRaw): RPCState { - return JSON.parse(data) +export function isRPCState(value: unknown): value is RPCState { + if (typeof value !== 'object' || value === null) return false + const v = value as Record + return ( + typeof v.event === 'string' && + typeof v.uuid === 'string' && + (v.type === 'event' || v.type === 'response') && + ENVIRONMENTS.includes(v.calledFrom) && + ENVIRONMENTS.includes(v.calledTo) && + (v.data === null || Array.isArray(v.data)) + ) +} + +/** + * **Internal** + * + * Parses a raw payload, `null` if it is not JSON or not an RPC payload + */ +export function parse(data: RPCStateRaw): RPCState | null { + try { + const value: unknown = JSON.parse(data) + return isRPCState(value) ? value : null + } catch { + return null + } } /** diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index 3e793df..d0fc19c 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -94,7 +94,6 @@ export enum RPCEvents { */ export enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', - INVALID_DATA = 'Invalid data (possibly broken JSON)', NO_PLAYER = 'No player (failed to resolve from local index)', UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', From 6247b7d7812c9522e3eb50536269381995faa673 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:28:36 +0100 Subject: [PATCH 16/33] fix(rpc): prevent client id spoofing --- rpc/src/core/base.ts | 44 ++++++++++++++++--- rpc/src/core/client.ts | 11 +---- rpc/src/core/server.ts | 91 ++++++++++++++-------------------------- rpc/src/fivem.d.ts | 7 +++- rpc/src/utils/pending.ts | 36 +++++++++------- rpc/src/utils/types.ts | 2 +- 6 files changed, 100 insertions(+), 91 deletions(-) diff --git a/rpc/src/core/base.ts b/rpc/src/core/base.ts index 3330db8..5024a05 100644 --- a/rpc/src/core/base.ts +++ b/rpc/src/core/base.ts @@ -4,13 +4,14 @@ import { notRegisteredMessage, RPCError, } from '../utils/errors' -import { generateUUID } from '../utils/funcs' +import { generateUUID, isRPCState, parse } from '../utils/funcs' import { Pending } from '../utils/pending' import { type RPCConfig, type RPCEnvironment, RPCErrors, type RPCState, + type RPCStateRaw, } from '../utils/types' export class RPCInstanceBase { @@ -36,7 +37,7 @@ export class RPCInstanceBase { emitter.on(event, cb) return this } - + /** Unregisters `cb` for `event` on `emitter`; `method` is only for logs */ protected unlisten(emitter: Emitter, method: string, event: string): this { this.log(`${method} ${event}`) @@ -76,15 +77,46 @@ export class RPCInstanceBase { return this._emitterLocal.emit(event, ...args) } - /** Settles the call waiting for `response`; ignores late or unexpected ones */ - protected settle(response: RPCState): void { + /** + * Settles the call waiting for `response`; ignores late or unexpected ones. + * + * @param peer - who sent the response (server: `source`) + */ + protected settle(response: RPCState, peer?: number): void { const found = response.error - ? this._pending.reject(response.uuid, RPCError.fromResponse(response)) - : this._pending.resolve(response.uuid, response.data?.[0]) + ? this._pending.reject( + response.uuid, + RPCError.fromResponse(response), + peer, + ) + : this._pending.resolve(response.uuid, response.data?.[0], peer) if (!found) this.logIgnored(response) } + /** + * Parses and validates an incoming payload. Anything else sent on the RPC + * channels (broken JSON, other shapes) is dropped: `null`. + */ + protected accept(input: RPCStateRaw | unknown): RPCState | null { + const payload = + typeof input === 'string' + ? parse(input as RPCStateRaw) + : isRPCState(input) + ? input + : null + + if (!payload) { + this.log(`dropped invalid payload ${String(input).slice(0, 200)}`) + return null + } + + this.log( + `accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, + ) + return payload + } + /** * Runs the listener for `request` and builds the response to send back. * Never throws: a missing listener or a thrown error ends up in `error`. diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 98bdd39..d718973 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -86,7 +86,6 @@ export class RPCInstanceClient extends RPCInstanceBase { return this.dispatch(this._emitterWeb, payload) } if (payload.calledTo === 'server') { - payload.player = GetPlayerServerId(PlayerId()) emitNet(RPCEvents.LISTENER_WEB, stringify(payload)) return this._pending.wait(payload) @@ -100,7 +99,6 @@ export class RPCInstanceClient extends RPCInstanceBase { return { status: 'ok' } } if (payload.calledTo === 'server') { - payload.player = GetPlayerServerId(PlayerId()) emitNet(RPCEvents.LISTENER_WEB, stringify(payload)) return { status: 'ok' } @@ -135,12 +133,7 @@ export class RPCInstanceClient extends RPCInstanceBase { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload = this.request( - eventName, - 'server', - args, - GetPlayerServerId(PlayerId()), - ) + const payload = this.request(eventName, 'server', args, null) emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload)) @@ -173,7 +166,7 @@ export class RPCInstanceClient extends RPCInstanceBase { Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { - const payload = this.request(eventName, 'webview', args, PlayerId()) + const payload = this.request(eventName, 'webview', args, null) this._sendWebMessage({ origin: RPCEvents.LISTENER_CLIENT, diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index b7ea9f3..977f2bf 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -25,71 +25,44 @@ export class RPCInstanceServer extends RPCInstanceBase { console.log('[RPC] Initialized Server') - onNet(RPCEvents.LISTENER_CLIENT, this._handleClient.bind(this)) - onNet(RPCEvents.LISTENER_WEB, this._handleWeb.bind(this)) + // `source` must be read synchronously, before any await + onNet(RPCEvents.LISTENER_CLIENT, (raw: RPCStateRaw) => + this._handle(raw, source, 'client', this._emitterClient), + ) + onNet(RPCEvents.LISTENER_WEB, (raw: RPCStateRaw) => + this._handle(raw, source, 'webview', this._emitterWeb), + ) } // ===== HANDLERS ===== - private async _handleClient(payloadRaw: RPCStateRaw) { + /** + * Handles a payload from a client or its webview. + * + * @param player - FiveM `source` of the net event: the only trusted player id, + * whatever the payload claims + */ + private async _handle( + payloadRaw: RPCStateRaw, + player: number, + from: 'client' | 'webview', + emitter: Emitter, + ) { const payload = this.accept(payloadRaw) - if (!payload) return + if (!payload || payload.calledFrom !== from) return - if (payload.calledFrom === 'client') { - if (payload.type === 'event') { - if (payload.player === null || payload.player === -1) { - // nobody to reply to, the caller times out - console.error( - new RPCError( - RPCErrors.NO_PLAYER, - `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, - ), - ) - return - } - - const response = await this.dispatch( - this._emitterClient, - payload, - payload.player, - ) - - emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) - } - if (payload.type === 'response') { - this.settle(payload) - } + // not sent by a player, e.g. a server-side trigger of the RPC channel + if (!(player > 0)) { + this.log(`dropped ${payload.event}: no player source`) + return } - } + payload.player = player - private async _handleWeb(payloadRaw: RPCStateRaw) { - const payload = this.accept(payloadRaw) - if (!payload) return - - if (payload.calledFrom === 'webview') { - if (payload.type === 'event') { - if (payload.player === null || payload.player === -1) { - // nobody to reply to, the caller times out - console.error( - new RPCError( - RPCErrors.NO_PLAYER, - `${RPCErrors.NO_PLAYER}: "${payload.event}" from ${payload.calledFrom}`, - ), - ) - return - } - - const response = await this.dispatch( - this._emitterWeb, - payload, - payload.player, - ) - - emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response)) - } - if (payload.type === 'response') { - this.settle(payload) - } + if (payload.type === 'event') { + const response = await this.dispatch(emitter, payload, player) + emitNet(RPCEvents.LISTENER_SERVER, player, stringify(response)) + } else { + this.settle(payload, player) } } @@ -128,7 +101,7 @@ export class RPCInstanceServer extends RPCInstanceBase { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload) + return this._pending.wait>(payload, player) } public async emitClientEveryone< @@ -175,7 +148,7 @@ export class RPCInstanceServer extends RPCInstanceBase { emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload) + return this._pending.wait>(payload, player) } // ===== SELF ===== diff --git a/rpc/src/fivem.d.ts b/rpc/src/fivem.d.ts index 3d9a00a..716b21b 100644 --- a/rpc/src/fivem.d.ts +++ b/rpc/src/fivem.d.ts @@ -21,11 +21,14 @@ declare function RegisterCommand( restricted: boolean, ): void +// ===== SERVER ===== + +/** Player that sent the current net event. Only valid synchronously in the handler */ +declare var source: number + // ===== CLIENT ===== declare function RegisterNuiCallbackType(callbackType: string): void -declare function GetPlayerServerId(player: number): number -declare function PlayerId(): number declare function SetNuiFocus(hasFocus: boolean, hasCursor: boolean): void declare function SendNuiMessage(jsonString: string): boolean diff --git a/rpc/src/utils/pending.ts b/rpc/src/utils/pending.ts index 07dd1ad..c2f419b 100644 --- a/rpc/src/utils/pending.ts +++ b/rpc/src/utils/pending.ts @@ -5,6 +5,8 @@ type Call = { resolve: (data: unknown) => void reject: (error: Error) => void timer: ReturnType | undefined + /** Only a response from this peer settles the call (server: target player) */ + peer: number | undefined } /** Calls waiting for a response, keyed by payload uuid. */ @@ -14,13 +16,18 @@ export class Pending { /** @param timeout - ms before a call rejects, `0` or less disables it */ constructor(private readonly _timeout: number) {} - /** Waits for the response to `request`, rejects with `RPCErrors.TIMEOUT` */ - public wait(request: RPCState): Promise { + /** + * Waits for the response to `request`, rejects with `RPCErrors.TIMEOUT`. + * + * @param peer - if set, only a response from this peer settles the call + */ + public wait(request: RPCState, peer?: number): Promise { return new Promise((resolve, reject) => { const timer = this._timeout > 0 ? setTimeout( - () => this.reject(request.uuid, this._timeoutError(request)), + () => + this.reject(request.uuid, this._timeoutError(request), peer), this._timeout, ) : undefined @@ -29,30 +36,31 @@ export class Pending { resolve: resolve as (data: unknown) => void, reject, timer, + peer, }) }) } - /** @returns `false` if no call waits for this uuid (late or unexpected response) */ - public resolve(uuid: string, data: unknown): boolean { - const call = this._take(uuid) + /** @returns `false` if no call from `peer` waits for this uuid (late or unexpected response) */ + public resolve(uuid: string, data: unknown, peer?: number): boolean { + const call = this._take(uuid, peer) call?.resolve(data) return call !== undefined } - /** @returns `false` if no call waits for this uuid */ - public reject(uuid: string, error: Error): boolean { - const call = this._take(uuid) + /** @returns `false` if no call from `peer` waits for this uuid */ + public reject(uuid: string, error: Error, peer?: number): boolean { + const call = this._take(uuid, peer) call?.reject(error) return call !== undefined } - private _take(uuid: string): Call | undefined { + private _take(uuid: string, peer: number | undefined): Call | undefined { const call = this._calls.get(uuid) - if (call) { - this._calls.delete(uuid) - clearTimeout(call.timer) - } + if (!call || (call.peer !== undefined && call.peer !== peer)) return + + this._calls.delete(uuid) + clearTimeout(call.timer) return call } diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index d0fc19c..f022201 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -48,6 +48,7 @@ export type RPCState = { calledTo: RPCEnvironment error: RPCErrorPayload | null data: unknown[] | null + /** Server id of the player involved. The server fills it from `source`, never trusting the sender */ player: number | null type: RPCEventType } @@ -94,7 +95,6 @@ export enum RPCEvents { */ export enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', - NO_PLAYER = 'No player (failed to resolve from local index)', UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', TIMEOUT = 'Timed out waiting for response', From c085fd2c8fea76165e5e29eb8ef1473be7172245 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:34:46 +0100 Subject: [PATCH 17/33] fix(rpc): separate public calls + better pending handlers --- rpc/src/core/base.ts | 3 ++- rpc/src/core/client.ts | 11 +++++++++-- rpc/src/core/server.ts | 4 ++-- rpc/src/utils/funcs.ts | 2 +- rpc/src/utils/types.ts | 10 ++++++++-- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/rpc/src/core/base.ts b/rpc/src/core/base.ts index 5024a05..8d15bcc 100644 --- a/rpc/src/core/base.ts +++ b/rpc/src/core/base.ts @@ -51,6 +51,7 @@ export class RPCInstanceBase { to: RPCEnvironment, args: unknown[], player: number | null, + type: 'event' | 'broadcast' = 'event', ): RPCState { return { event, @@ -60,7 +61,7 @@ export class RPCInstanceBase { error: null, data: args, player, - type: 'event', + type, } } diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index d718973..43543fe 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -54,11 +54,18 @@ export class RPCInstanceClient extends RPCInstanceBase { const payload = this.accept(payloadRaw) if (!payload) return - if (payload.type === 'event') { + if (payload.type === 'event' || payload.type === 'broadcast') { if (payload.calledTo === 'client') { const response = await this.dispatch(this._emitterServer, payload) - emitNet(RPCEvents.LISTENER_CLIENT, stringify(response)) + if (payload.type === 'event') { + emitNet(RPCEvents.LISTENER_CLIENT, stringify(response)) + } else if (response.error) { + // nobody waits for a broadcast, so its failures only show up here + this.log( + `broadcast ${payload.event} failed: ${response.error.message}`, + ) + } } if (payload.calledTo === 'webview') { this._sendWebMessage({ diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 977f2bf..f1def5f 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -61,7 +61,7 @@ export class RPCInstanceServer extends RPCInstanceBase { if (payload.type === 'event') { const response = await this.dispatch(emitter, payload, player) emitNet(RPCEvents.LISTENER_SERVER, player, stringify(response)) - } else { + } else if (payload.type === 'response') { this.settle(payload, player) } } @@ -108,7 +108,7 @@ export class RPCInstanceServer extends RPCInstanceBase { EventName extends keyof s.RPCEvents_ServerClient, Arguments extends Parameters, >(eventName: EventName, ...args: Arguments): Promise { - const payload = this.request(eventName, 'client', args, -1) + const payload = this.request(eventName, 'client', args, -1, 'broadcast') emitNet(RPCEvents.LISTENER_SERVER, -1, stringify(payload)) } diff --git a/rpc/src/utils/funcs.ts b/rpc/src/utils/funcs.ts index e2425a3..daf5e67 100644 --- a/rpc/src/utils/funcs.ts +++ b/rpc/src/utils/funcs.ts @@ -24,7 +24,7 @@ export function isRPCState(value: unknown): value is RPCState { return ( typeof v.event === 'string' && typeof v.uuid === 'string' && - (v.type === 'event' || v.type === 'response') && + (v.type === 'event' || v.type === 'response' || v.type === 'broadcast') && ENVIRONMENTS.includes(v.calledFrom) && ENVIRONMENTS.includes(v.calledTo) && (v.data === null || Array.isArray(v.data)) diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index f022201..ca3ba44 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -33,8 +33,14 @@ export type RPCConfig = { timeout?: number } -/** **Internal** */ -export type RPCEventType = 'event' | 'response' +/** + * **Internal** + * + * - `event`: call that expects a `response` + * - `response`: answer to an `event` + * - `broadcast`: one-way event, receivers do not reply + */ +export type RPCEventType = 'event' | 'response' | 'broadcast' /** * **Internal** From 3e3548a9cc214e53a446e778b7f4944863660288 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:38:46 +0100 Subject: [PATCH 18/33] fix(rpc): rework native events + dedupe --- rpc/src/core/client.ts | 18 +- rpc/src/core/server.ts | 7 +- rpc/src/utils/errors.ts | 5 + rpc/src/utils/native.ts | 631 ++++++++++++++++++++-------------------- rpc/src/utils/types.ts | 279 +----------------- 5 files changed, 344 insertions(+), 596 deletions(-) diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 43543fe..244076a 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -1,7 +1,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' -import { RPCError } from '../utils/errors' +import { RPCError, unknownNativeMessage } from '../utils/errors' import { stringify, stringifyWeb } from '../utils/funcs' import { NATIVE_CLIENT_EVENTS, @@ -12,7 +12,7 @@ import { RPCErrors, RPCEvents, type RPCNativeClientEvents, - type RPCNativeClientNetworksEvents, + type RPCNativeClientNetworkEvents, type RPCState, type RPCStateRaw, type RPCStateWeb, @@ -233,7 +233,10 @@ export class RPCInstanceClient extends RPCInstanceBase { CallbackArguments extends Parameters, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_CLIENT_EVENTS.includes(eventName)) { - throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) + throw new RPCError( + RPCErrors.UNKNOWN_NATIVE, + unknownNativeMessage(eventName, 'NATIVE_CLIENT_EVENTS'), + ) } this.log(`onNativeEvent ${eventName}`) @@ -244,13 +247,16 @@ export class RPCInstanceClient extends RPCInstanceBase { } public onNativeNetworkEvent< - EventName extends keyof RPCNativeClientNetworksEvents, + EventName extends keyof RPCNativeClientNetworkEvents, CallbackArguments extends Parameters< - RPCNativeClientNetworksEvents[EventName] + RPCNativeClientNetworkEvents[EventName] >, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) { - throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) + throw new RPCError( + RPCErrors.UNKNOWN_NATIVE, + unknownNativeMessage(eventName, 'NATIVE_CLIENT_NETWORK_EVENTS'), + ) } this.log(`onNativeNetworkEvent ${eventName}`) diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index f1def5f..0e3d76e 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -1,7 +1,7 @@ import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' -import { RPCError } from '../utils/errors' +import { RPCError, unknownNativeMessage } from '../utils/errors' import { stringify } from '../utils/funcs' import { NATIVE_SERVER_EVENTS } from '../utils/native' import { @@ -202,7 +202,10 @@ export class RPCInstanceServer extends RPCInstanceBase { CallbackArguments extends Parameters, >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { if (!NATIVE_SERVER_EVENTS.includes(eventName)) { - throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE) + throw new RPCError( + RPCErrors.UNKNOWN_NATIVE, + unknownNativeMessage(eventName, 'NATIVE_SERVER_EVENTS'), + ) } this.log(`onNativeEvent ${eventName}`) diff --git a/rpc/src/utils/errors.ts b/rpc/src/utils/errors.ts index 193905c..ab4eb05 100644 --- a/rpc/src/utils/errors.ts +++ b/rpc/src/utils/errors.ts @@ -66,6 +66,11 @@ export function notRegisteredMessage( return `No listener for "${event}" on ${receiver}. Register it with rpc.${listenerName(receiver, sender)}("${event}", ...) in ${receiver} code.` } +/** **Internal** */ +export function unknownNativeMessage(event: string, list: string): string { + return `"${event}" is not in ${list}. Events not listed there can be registered with FiveM's on("${event}", ...) directly.` +} + /** **Internal** */ export function handlerErrorMessage( event: string, diff --git a/rpc/src/utils/native.ts b/rpc/src/utils/native.ts index ab739c9..349c354 100644 --- a/rpc/src/utils/native.ts +++ b/rpc/src/utils/native.ts @@ -1,321 +1,322 @@ -import type { - RPCNativeClientEvents, - RPCNativeClientNetworkEventsNames, - RPCNativeServerEvents, -} from './types' +import type { RPCNativeClientEvents, RPCNativeServerEvents } from './types' -/** - * https://docs.fivem.net/docs/scripting-reference/events/server-events/ - * @readonly - */ -export const NATIVE_SERVER_EVENTS: readonly (keyof RPCNativeServerEvents)[] = [ - 'entityCreated', - 'entityCreating', - 'entityRemoved', - 'onResourceListRefresh', - 'onResourceStart', - 'onResourceStarting', - 'onResourceStop', - 'onServerResourceStart', - 'onServerResourceStop', - 'playerConnecting', - 'playerEnteredScope', - 'playerJoining', - 'playerLeftScope', - 'ptFxEvent', - 'removeAllWeaponsEvent', - 'startProjectileEvent', - 'weaponDamageEvent', -] as const +// Server and client lists are built from an object that must name exactly the +// keys of the typed event map, so the list and the types cannot drift apart. -/** - * https://docs.fivem.net/docs/scripting-reference/events/client-events/ - * @readonly - */ -export const NATIVE_CLIENT_EVENTS: readonly (keyof RPCNativeClientEvents)[] = [ - 'entityDamaged', - 'gameEventTriggered', - 'mumbleConnected', - 'mumbleDisconnected', - 'onClientResourceStart', - 'onClientResourceStop', - 'onResourceStart', - 'onResourceStarting', - 'onResourceStop', - 'populationPedCreating', -] as const +const serverEvents = { + entityCreated: true, + entityCreating: true, + entityRemoved: true, + onResourceListRefresh: true, + onResourceStart: true, + onResourceStarting: true, + onResourceStop: true, + onServerResourceStart: true, + onServerResourceStop: true, + playerConnecting: true, + playerEnteredScope: true, + playerJoining: true, + playerLeftScope: true, + ptFxEvent: true, + removeAllWeaponsEvent: true, + startProjectileEvent: true, + weaponDamageEvent: true, +} satisfies Record + +/** https://docs.fivem.net/docs/scripting-reference/events/server-events/ */ +export const NATIVE_SERVER_EVENTS = Object.keys( + serverEvents, +) as readonly (keyof RPCNativeServerEvents)[] + +const clientEvents = { + entityDamaged: true, + gameEventTriggered: true, + mumbleConnected: true, + mumbleDisconnected: true, + onClientResourceStart: true, + onClientResourceStop: true, + onResourceStart: true, + onResourceStarting: true, + onResourceStop: true, + populationPedCreating: true, +} satisfies Record + +/** https://docs.fivem.net/docs/scripting-reference/events/client-events/ */ +export const NATIVE_CLIENT_EVENTS = Object.keys( + clientEvents, +) as readonly (keyof RPCNativeClientEvents)[] /** * https://docs.fivem.net/docs/game-references/game-events/ - * @readonly + * + * Source of truth for `RPCNativeClientNetworkEventsNames`. */ -export const NATIVE_CLIENT_NETWORK_EVENTS: readonly RPCNativeClientNetworkEventsNames[] = - [ - 'CEventAcquaintancePed', - 'CEventAcquaintancePedDead', - 'CEventAcquaintancePedDislike', - 'CEventAcquaintancePedHate', - 'CEventAcquaintancePedLike', - 'CEventAcquaintancePedWanted', - 'CEventAgitated', - 'CEventAgitatedAction', - 'CEventCallForCover', - 'CEventCarUndriveable', - 'CEventClimbLadderOnRoute', - 'CEventClimbNavMeshOnRoute', - 'CEventCombatTaunt', - 'CEventCommunicateEvent', - 'CEventCopCarBeingStolen', - 'CEventCrimeCryForHelp', - 'CEventCrimeReported', - 'CEventDamage', - 'CEventDataDecisionMaker', - 'CEventDataFileMounter', - 'CEventDataResponseAggressiveRubberneck', - 'CEventDataResponseDeferToScenarioPointFlags', - 'CEventDataResponseFriendlyAimedAt', - 'CEventDataResponseFriendlyNearMiss', - 'CEventDataResponsePlayerDeath', - 'CEventDataResponsePoliceTaskWanted', - 'CEventDataResponseSwatTaskWanted', - 'CEventDataResponseTask', - 'CEventDataResponseTaskAgitated', - 'CEventDataResponseTaskCombat', - 'CEventDataResponseTaskCower', - 'CEventDataResponseTaskCrouch', - 'CEventDataResponseTaskDuckAndCover', - 'CEventDataResponseTaskEscapeBlast', - 'CEventDataResponseTaskEvasiveStep', - 'CEventDataResponseTaskExhaustedFlee', - 'CEventDataResponseTaskExplosion', - 'CEventDataResponseTaskFlee', - 'CEventDataResponseTaskFlyAway', - 'CEventDataResponseTaskGrowlAndFlee', - 'CEventDataResponseTaskGunAimedAt', - 'CEventDataResponseTaskHandsUp', - 'CEventDataResponseTaskHeadTrack', - 'CEventDataResponseTaskLeaveCarAndFlee', - 'CEventDataResponseTaskScenarioFlee', - 'CEventDataResponseTaskSharkAttack', - 'CEventDataResponseTaskShockingEventBackAway', - 'CEventDataResponseTaskShockingEventGoto', - 'CEventDataResponseTaskShockingEventHurryAway', - 'CEventDataResponseTaskShockingEventReact', - 'CEventDataResponseTaskShockingEventReactToAircraft', - 'CEventDataResponseTaskShockingEventStopAndStare', - 'CEventDataResponseTaskShockingEventThreatResponse', - 'CEventDataResponseTaskShockingEventWatch', - 'CEventDataResponseTaskShockingNiceCar', - 'CEventDataResponseTaskShockingPoliceInvestigate', - 'CEventDataResponseTaskThreat', - 'CEventDataResponseTaskTurnToFace', - 'CEventDataResponseTaskWalkAway', - 'CEventDataResponseTaskWalkRoundEntity', - 'CEventDataResponseTaskWalkRoundFire', - 'CEventDeadPedFound', - 'CEventDeath', - 'CEventDecisionMakerResponse', - 'CEventDisturbance', - 'CEventDraggedOutCar', - 'CEventEditableResponse', - 'CEventEncroachingPed', - 'CEventEntityDamaged', - 'CEventEntityDestroyed', - 'CEventExplosion', - 'CEventExplosionHeard', - 'CEventFireNearby', - 'CEventFootStepHeard', - 'CEventFriendlyAimedAt', - 'CEventFriendlyFireNearMiss', - 'CEventGetOutOfWater', - 'CEventGivePedTask', - 'CEventGroupScriptAI', - 'CEventGroupScriptNetwork', - 'CEventGunAimedAt', - 'CEventGunShot', - 'CEventGunShotBulletImpact', - 'CEventGunShotWhizzedBy', - 'CEventHelpAmbientFriend', - 'CEventHurtTransition', - 'CEventInAir', - 'CEventInfo', - 'CEventInfoBase', - 'CEventInjuredCryForHelp', - 'CEventLeaderEnteredCarAsDriver', - 'CEventLeaderExitedCarAsDriver', - 'CEventLeaderHolsteredWeapon', - 'CEventLeaderLeftCover', - 'CEventLeaderUnholsteredWeapon', - 'CEventMeleeAction', - 'CEventMustLeaveBoat', - 'CEventNetworkAdminInvited', - 'CEventNetworkAttemptHostMigration', - 'CEventNetworkBail', - 'CEventNetworkCashTransactionLog', - 'CEventNetworkCheatTriggered', - 'CEventNetworkClanInviteReceived', - 'CEventNetworkClanJoined', - 'CEventNetworkClanKicked', - 'CEventNetworkClanLeft', - 'CEventNetworkClanRankChanged', - 'CEventNetworkCloudEvent', - 'CEventNetworkCloudFileResponse', - 'CEventNetworkEmailReceivedEvent', - 'CEventNetworkEndMatch', - 'CEventNetworkEndSession', - 'CEventNetworkEntityDamage', - 'CEventNetworkFindSession', - 'CEventNetworkFollowInviteReceived', - 'CEventNetworkHostMigration', - 'CEventNetworkHostSession', - 'CEventNetworkIncrementStat', - 'CEventNetworkInviteAccepted', - 'CEventNetworkInviteConfirmed', - 'CEventNetworkInviteRejected', - 'CEventNetworkJoinSession', - 'CEventNetworkJoinSessionResponse', - 'CEventNetworkOnlinePermissionsUpdated', - 'CEventNetworkPedLeftBehind', - 'CEventNetworkPickupRespawned', - 'CEventNetworkPlayerArrest', - 'CEventNetworkPlayerCollectedAmbientPickup', - 'CEventNetworkPlayerCollectedPickup', - 'CEventNetworkPlayerCollectedPortablePickup', - 'CEventNetworkPlayerDroppedPortablePickup', - 'CEventNetworkPlayerEnteredVehicle', - 'CEventNetworkPlayerJoinScript', - 'CEventNetworkPlayerLeftScript', - 'CEventNetworkPlayerScript', - 'CEventNetworkPlayerSession', - 'CEventNetworkPlayerSpawn', - 'CEventNetworkPresenceInvite', - 'CEventNetworkPresenceInviteRemoved', - 'CEventNetworkPresenceInviteReply', - 'CEventNetworkPresenceTriggerEvent', - 'CEventNetworkPresence_StatUpdate', - 'CEventNetworkPrimaryClanChanged', - 'CEventNetworkRequestDelay', - 'CEventNetworkRosChanged', - 'CEventNetworkScAdminPlayerUpdated', - 'CEventNetworkScAdminReceivedCash', - 'CEventNetworkScriptEvent', - 'CEventNetworkSessionEvent', - 'CEventNetworkShopTransaction', - 'CEventNetworkSignInStateChanged', - 'CEventNetworkSocialClubAccountLinked', - 'CEventNetworkSpectateLocal', - 'CEventNetworkStartMatch', - 'CEventNetworkStartSession', - 'CEventNetworkStorePlayerLeft', - 'CEventNetworkSummon', - 'CEventNetworkSystemServiceEvent', - 'CEventNetworkTextMessageReceived', - 'CEventNetworkTimedExplosion', - 'CEventNetworkTransitionEvent', - 'CEventNetworkTransitionGamerInstruction', - 'CEventNetworkTransitionMemberJoined', - 'CEventNetworkTransitionMemberLeft', - 'CEventNetworkTransitionParameterChanged', - 'CEventNetworkTransitionStarted', - 'CEventNetworkTransitionStringChanged', - 'CEventNetworkVehicleUndrivable', - 'CEventNetworkVoiceConnectionRequested', - 'CEventNetworkVoiceConnectionResponse', - 'CEventNetworkVoiceConnectionTerminated', - 'CEventNetworkVoiceSessionEnded', - 'CEventNetworkVoiceSessionStarted', - 'CEventNetworkWithData', - 'CEventNetwork_InboxMsgReceived', - 'CEventNewTask', - 'CEventObjectCollision', - 'CEventOnFire', - 'CEventOpenDoor', - 'CEventPedCollisionWithPed', - 'CEventPedCollisionWithPlayer', - 'CEventPedEnteredMyVehicle', - 'CEventPedJackingMyVehicle', - 'CEventPedOnCarRoof', - 'CEventPedSeenDeadPed', - 'CEventPlayerCollisionWithPed', - 'CEventPlayerDeath', - 'CEventPlayerUnableToEnterVehicle', - 'CEventPotentialBeWalkedInto', - 'CEventPotentialBlast', - 'CEventPotentialGetRunOver', - 'CEventPotentialWalkIntoVehicle', - 'CEventProvidingCover', - 'CEventRanOverPed', - 'CEventReactionEnemyPed', - 'CEventReactionInvestigateDeadPed', - 'CEventReactionInvestigateThreat', - 'CEventRequestHelp', - 'CEventRequestHelpWithConfrontation', - 'CEventRespondedToThreat', - 'CEventScanner', - 'CEventScenarioForceAction', - 'CEventScriptCommand', - 'CEventScriptWithData', - 'CEventShocking', - 'CEventShockingBicycleCrash', - 'CEventShockingBicycleOnPavement', - 'CEventShockingCarAlarm', - 'CEventShockingCarChase', - 'CEventShockingCarCrash', - 'CEventShockingCarOnCar', - 'CEventShockingCarPileUp', - 'CEventShockingDangerousAnimal', - 'CEventShockingDeadBody', - 'CEventShockingDrivingOnPavement', - 'CEventShockingEngineRevved', - 'CEventShockingExplosion', - 'CEventShockingFire', - 'CEventShockingGunFight', - 'CEventShockingGunshotFired', - 'CEventShockingHelicopterOverhead', - 'CEventShockingHornSounded', - 'CEventShockingInDangerousVehicle', - 'CEventShockingInjuredPed', - 'CEventShockingMadDriver', - 'CEventShockingMadDriverBicycle', - 'CEventShockingMadDriverExtreme', - 'CEventShockingMugging', - 'CEventShockingNonViolentWeaponAimedAt', - 'CEventShockingParachuterOverhead', - 'CEventShockingPedKnockedIntoByPlayer', - 'CEventShockingPedRunOver', - 'CEventShockingPedShot', - 'CEventShockingPlaneFlyby', - 'CEventShockingPotentialBlast', - 'CEventShockingPropertyDamage', - 'CEventShockingRunningPed', - 'CEventShockingRunningStampede', - 'CEventShockingSeenCarStolen', - 'CEventShockingSeenConfrontation', - 'CEventShockingSeenGangFight', - 'CEventShockingSeenInsult', - 'CEventShockingSeenMeleeAction', - 'CEventShockingSeenNiceCar', - 'CEventShockingSeenPedKilled', - 'CEventShockingSiren', - 'CEventShockingStudioBomb', - 'CEventShockingVehicleTowed', - 'CEventShockingVisibleWeapon', - 'CEventShockingWeaponThreat', - 'CEventShockingWeirdPed', - 'CEventShockingWeirdPedApproaching', - 'CEventShoutBlockingLos', - 'CEventShoutTargetPosition', - 'CEventShovePed', - 'CEventSoundBase', - 'CEventStatChangedValue', - 'CEventStaticCountReachedMax', - 'CEventStuckInAir', - 'CEventSuspiciousActivity', - 'CEventSwitch2NM', - 'CEventUnidentifiedPed', - 'CEventVehicleCollision', - 'CEventVehicleDamage', - 'CEventVehicleDamageWeapon', - 'CEventVehicleOnFire', - 'CEventWrithe', - ] as const +export const NATIVE_CLIENT_NETWORK_EVENTS = [ + 'CEventAcquaintancePed', + 'CEventAcquaintancePedDead', + 'CEventAcquaintancePedDislike', + 'CEventAcquaintancePedHate', + 'CEventAcquaintancePedLike', + 'CEventAcquaintancePedWanted', + 'CEventAgitated', + 'CEventAgitatedAction', + 'CEventCallForCover', + 'CEventCarUndriveable', + 'CEventClimbLadderOnRoute', + 'CEventClimbNavMeshOnRoute', + 'CEventCombatTaunt', + 'CEventCommunicateEvent', + 'CEventCopCarBeingStolen', + 'CEventCrimeCryForHelp', + 'CEventCrimeReported', + 'CEventDamage', + 'CEventDataDecisionMaker', + 'CEventDataFileMounter', + 'CEventDataResponseAggressiveRubberneck', + 'CEventDataResponseDeferToScenarioPointFlags', + 'CEventDataResponseFriendlyAimedAt', + 'CEventDataResponseFriendlyNearMiss', + 'CEventDataResponsePlayerDeath', + 'CEventDataResponsePoliceTaskWanted', + 'CEventDataResponseSwatTaskWanted', + 'CEventDataResponseTask', + 'CEventDataResponseTaskAgitated', + 'CEventDataResponseTaskCombat', + 'CEventDataResponseTaskCower', + 'CEventDataResponseTaskCrouch', + 'CEventDataResponseTaskDuckAndCover', + 'CEventDataResponseTaskEscapeBlast', + 'CEventDataResponseTaskEvasiveStep', + 'CEventDataResponseTaskExhaustedFlee', + 'CEventDataResponseTaskExplosion', + 'CEventDataResponseTaskFlee', + 'CEventDataResponseTaskFlyAway', + 'CEventDataResponseTaskGrowlAndFlee', + 'CEventDataResponseTaskGunAimedAt', + 'CEventDataResponseTaskHandsUp', + 'CEventDataResponseTaskHeadTrack', + 'CEventDataResponseTaskLeaveCarAndFlee', + 'CEventDataResponseTaskScenarioFlee', + 'CEventDataResponseTaskSharkAttack', + 'CEventDataResponseTaskShockingEventBackAway', + 'CEventDataResponseTaskShockingEventGoto', + 'CEventDataResponseTaskShockingEventHurryAway', + 'CEventDataResponseTaskShockingEventReact', + 'CEventDataResponseTaskShockingEventReactToAircraft', + 'CEventDataResponseTaskShockingEventStopAndStare', + 'CEventDataResponseTaskShockingEventThreatResponse', + 'CEventDataResponseTaskShockingEventWatch', + 'CEventDataResponseTaskShockingNiceCar', + 'CEventDataResponseTaskShockingPoliceInvestigate', + 'CEventDataResponseTaskThreat', + 'CEventDataResponseTaskTurnToFace', + 'CEventDataResponseTaskWalkAway', + 'CEventDataResponseTaskWalkRoundEntity', + 'CEventDataResponseTaskWalkRoundFire', + 'CEventDeadPedFound', + 'CEventDeath', + 'CEventDecisionMakerResponse', + 'CEventDisturbance', + 'CEventDraggedOutCar', + 'CEventEditableResponse', + 'CEventEncroachingPed', + 'CEventEntityDamaged', + 'CEventEntityDestroyed', + 'CEventExplosion', + 'CEventExplosionHeard', + 'CEventFireNearby', + 'CEventFootStepHeard', + 'CEventFriendlyAimedAt', + 'CEventFriendlyFireNearMiss', + 'CEventGetOutOfWater', + 'CEventGivePedTask', + 'CEventGroupScriptAI', + 'CEventGroupScriptNetwork', + 'CEventGunAimedAt', + 'CEventGunShot', + 'CEventGunShotBulletImpact', + 'CEventGunShotWhizzedBy', + 'CEventHelpAmbientFriend', + 'CEventHurtTransition', + 'CEventInAir', + 'CEventInfo', + 'CEventInfoBase', + 'CEventInjuredCryForHelp', + 'CEventLeaderEnteredCarAsDriver', + 'CEventLeaderExitedCarAsDriver', + 'CEventLeaderHolsteredWeapon', + 'CEventLeaderLeftCover', + 'CEventLeaderUnholsteredWeapon', + 'CEventMeleeAction', + 'CEventMustLeaveBoat', + 'CEventNetworkAdminInvited', + 'CEventNetworkAttemptHostMigration', + 'CEventNetworkBail', + 'CEventNetworkCashTransactionLog', + 'CEventNetworkCheatTriggered', + 'CEventNetworkClanInviteReceived', + 'CEventNetworkClanJoined', + 'CEventNetworkClanKicked', + 'CEventNetworkClanLeft', + 'CEventNetworkClanRankChanged', + 'CEventNetworkCloudEvent', + 'CEventNetworkCloudFileResponse', + 'CEventNetworkEmailReceivedEvent', + 'CEventNetworkEndMatch', + 'CEventNetworkEndSession', + 'CEventNetworkEntityDamage', + 'CEventNetworkFindSession', + 'CEventNetworkFollowInviteReceived', + 'CEventNetworkHostMigration', + 'CEventNetworkHostSession', + 'CEventNetworkIncrementStat', + 'CEventNetworkInviteAccepted', + 'CEventNetworkInviteConfirmed', + 'CEventNetworkInviteRejected', + 'CEventNetworkJoinSession', + 'CEventNetworkJoinSessionResponse', + 'CEventNetworkOnlinePermissionsUpdated', + 'CEventNetworkPedLeftBehind', + 'CEventNetworkPickupRespawned', + 'CEventNetworkPlayerArrest', + 'CEventNetworkPlayerCollectedAmbientPickup', + 'CEventNetworkPlayerCollectedPickup', + 'CEventNetworkPlayerCollectedPortablePickup', + 'CEventNetworkPlayerDroppedPortablePickup', + 'CEventNetworkPlayerEnteredVehicle', + 'CEventNetworkPlayerJoinScript', + 'CEventNetworkPlayerLeftScript', + 'CEventNetworkPlayerScript', + 'CEventNetworkPlayerSession', + 'CEventNetworkPlayerSpawn', + 'CEventNetworkPresenceInvite', + 'CEventNetworkPresenceInviteRemoved', + 'CEventNetworkPresenceInviteReply', + 'CEventNetworkPresenceTriggerEvent', + 'CEventNetworkPresence_StatUpdate', + 'CEventNetworkPrimaryClanChanged', + 'CEventNetworkRequestDelay', + 'CEventNetworkRosChanged', + 'CEventNetworkScAdminPlayerUpdated', + 'CEventNetworkScAdminReceivedCash', + 'CEventNetworkScriptEvent', + 'CEventNetworkSessionEvent', + 'CEventNetworkShopTransaction', + 'CEventNetworkSignInStateChanged', + 'CEventNetworkSocialClubAccountLinked', + 'CEventNetworkSpectateLocal', + 'CEventNetworkStartMatch', + 'CEventNetworkStartSession', + 'CEventNetworkStorePlayerLeft', + 'CEventNetworkSummon', + 'CEventNetworkSystemServiceEvent', + 'CEventNetworkTextMessageReceived', + 'CEventNetworkTimedExplosion', + 'CEventNetworkTransitionEvent', + 'CEventNetworkTransitionGamerInstruction', + 'CEventNetworkTransitionMemberJoined', + 'CEventNetworkTransitionMemberLeft', + 'CEventNetworkTransitionParameterChanged', + 'CEventNetworkTransitionStarted', + 'CEventNetworkTransitionStringChanged', + 'CEventNetworkVehicleUndrivable', + 'CEventNetworkVoiceConnectionRequested', + 'CEventNetworkVoiceConnectionResponse', + 'CEventNetworkVoiceConnectionTerminated', + 'CEventNetworkVoiceSessionEnded', + 'CEventNetworkVoiceSessionStarted', + 'CEventNetworkWithData', + 'CEventNetwork_InboxMsgReceived', + 'CEventNewTask', + 'CEventObjectCollision', + 'CEventOnFire', + 'CEventOpenDoor', + 'CEventPedCollisionWithPed', + 'CEventPedCollisionWithPlayer', + 'CEventPedEnteredMyVehicle', + 'CEventPedJackingMyVehicle', + 'CEventPedOnCarRoof', + 'CEventPedSeenDeadPed', + 'CEventPlayerCollisionWithPed', + 'CEventPlayerDeath', + 'CEventPlayerUnableToEnterVehicle', + 'CEventPotentialBeWalkedInto', + 'CEventPotentialBlast', + 'CEventPotentialGetRunOver', + 'CEventPotentialWalkIntoVehicle', + 'CEventProvidingCover', + 'CEventRanOverPed', + 'CEventReactionEnemyPed', + 'CEventReactionInvestigateDeadPed', + 'CEventReactionInvestigateThreat', + 'CEventRequestHelp', + 'CEventRequestHelpWithConfrontation', + 'CEventRespondedToThreat', + 'CEventScanner', + 'CEventScenarioForceAction', + 'CEventScriptCommand', + 'CEventScriptWithData', + 'CEventShocking', + 'CEventShockingBicycleCrash', + 'CEventShockingBicycleOnPavement', + 'CEventShockingCarAlarm', + 'CEventShockingCarChase', + 'CEventShockingCarCrash', + 'CEventShockingCarOnCar', + 'CEventShockingCarPileUp', + 'CEventShockingDangerousAnimal', + 'CEventShockingDeadBody', + 'CEventShockingDrivingOnPavement', + 'CEventShockingEngineRevved', + 'CEventShockingExplosion', + 'CEventShockingFire', + 'CEventShockingGunFight', + 'CEventShockingGunshotFired', + 'CEventShockingHelicopterOverhead', + 'CEventShockingHornSounded', + 'CEventShockingInDangerousVehicle', + 'CEventShockingInjuredPed', + 'CEventShockingMadDriver', + 'CEventShockingMadDriverBicycle', + 'CEventShockingMadDriverExtreme', + 'CEventShockingMugging', + 'CEventShockingNonViolentWeaponAimedAt', + 'CEventShockingParachuterOverhead', + 'CEventShockingPedKnockedIntoByPlayer', + 'CEventShockingPedRunOver', + 'CEventShockingPedShot', + 'CEventShockingPlaneFlyby', + 'CEventShockingPotentialBlast', + 'CEventShockingPropertyDamage', + 'CEventShockingRunningPed', + 'CEventShockingRunningStampede', + 'CEventShockingSeenCarStolen', + 'CEventShockingSeenConfrontation', + 'CEventShockingSeenGangFight', + 'CEventShockingSeenInsult', + 'CEventShockingSeenMeleeAction', + 'CEventShockingSeenNiceCar', + 'CEventShockingSeenPedKilled', + 'CEventShockingSiren', + 'CEventShockingStudioBomb', + 'CEventShockingVehicleTowed', + 'CEventShockingVisibleWeapon', + 'CEventShockingWeaponThreat', + 'CEventShockingWeirdPed', + 'CEventShockingWeirdPedApproaching', + 'CEventShoutBlockingLos', + 'CEventShoutTargetPosition', + 'CEventShovePed', + 'CEventSoundBase', + 'CEventStatChangedValue', + 'CEventStaticCountReachedMax', + 'CEventStuckInAir', + 'CEventSuspiciousActivity', + 'CEventSwitch2NM', + 'CEventUnidentifiedPed', + 'CEventVehicleCollision', + 'CEventVehicleDamage', + 'CEventVehicleDamageWeapon', + 'CEventVehicleOnFire', + 'CEventWrithe', +] as const diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index ca3ba44..221375a 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -1,6 +1,7 @@ import type { RPCInstanceClient } from '../core/client' import type { RPCInstanceServer } from '../core/server' import type { RPCInstanceWebview } from '../core/webview' +import type { NATIVE_CLIENT_NETWORK_EVENTS } from './native' /** * Possible environment states for `RPCConfig` @@ -101,7 +102,7 @@ export enum RPCEvents { */ export enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', - UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', + UNKNOWN_NATIVE = 'Unknown native event', UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', TIMEOUT = 'Timed out waiting for response', HANDLER_ERROR = 'Listener threw an error', @@ -252,7 +253,7 @@ export type RPCNativeClientEvents = { baseDamage: number, ): void gameEventTriggered( - name: RPCNativeClientNetworksEvents | string, + name: RPCNativeClientNetworkEventsNames | (string & {}), data: number[], ): void mumbleConnected(address: string, reconnecting: boolean): void @@ -274,7 +275,7 @@ export type RPCNativeClientEvents = { ): void } -export type RPCNativeClientNetworksEvents = { +export type RPCNativeClientNetworkEvents = { [name in RPCNativeClientNetworkEventsNames]: ( entities: number[], eventEntity: number, @@ -282,274 +283,6 @@ export type RPCNativeClientNetworksEvents = { ) => void } -/** - * https://docs.fivem.net/docs/game-references/game-events/ - */ +/** https://docs.fivem.net/docs/game-references/game-events/ */ export type RPCNativeClientNetworkEventsNames = - | 'CEventAcquaintancePed' - | 'CEventAcquaintancePedDead' - | 'CEventAcquaintancePedDislike' - | 'CEventAcquaintancePedHate' - | 'CEventAcquaintancePedLike' - | 'CEventAcquaintancePedWanted' - | 'CEventAgitated' - | 'CEventAgitatedAction' - | 'CEventCallForCover' - | 'CEventCarUndriveable' - | 'CEventClimbLadderOnRoute' - | 'CEventClimbNavMeshOnRoute' - | 'CEventCombatTaunt' - | 'CEventCommunicateEvent' - | 'CEventCopCarBeingStolen' - | 'CEventCrimeCryForHelp' - | 'CEventCrimeReported' - | 'CEventDamage' - | 'CEventDataDecisionMaker' - | 'CEventDataFileMounter' - | 'CEventDataResponseAggressiveRubberneck' - | 'CEventDataResponseDeferToScenarioPointFlags' - | 'CEventDataResponseFriendlyAimedAt' - | 'CEventDataResponseFriendlyNearMiss' - | 'CEventDataResponsePlayerDeath' - | 'CEventDataResponsePoliceTaskWanted' - | 'CEventDataResponseSwatTaskWanted' - | 'CEventDataResponseTask' - | 'CEventDataResponseTaskAgitated' - | 'CEventDataResponseTaskCombat' - | 'CEventDataResponseTaskCower' - | 'CEventDataResponseTaskCrouch' - | 'CEventDataResponseTaskDuckAndCover' - | 'CEventDataResponseTaskEscapeBlast' - | 'CEventDataResponseTaskEvasiveStep' - | 'CEventDataResponseTaskExhaustedFlee' - | 'CEventDataResponseTaskExplosion' - | 'CEventDataResponseTaskFlee' - | 'CEventDataResponseTaskFlyAway' - | 'CEventDataResponseTaskGrowlAndFlee' - | 'CEventDataResponseTaskGunAimedAt' - | 'CEventDataResponseTaskHandsUp' - | 'CEventDataResponseTaskHeadTrack' - | 'CEventDataResponseTaskLeaveCarAndFlee' - | 'CEventDataResponseTaskScenarioFlee' - | 'CEventDataResponseTaskSharkAttack' - | 'CEventDataResponseTaskShockingEventBackAway' - | 'CEventDataResponseTaskShockingEventGoto' - | 'CEventDataResponseTaskShockingEventHurryAway' - | 'CEventDataResponseTaskShockingEventReact' - | 'CEventDataResponseTaskShockingEventReactToAircraft' - | 'CEventDataResponseTaskShockingEventStopAndStare' - | 'CEventDataResponseTaskShockingEventThreatResponse' - | 'CEventDataResponseTaskShockingEventWatch' - | 'CEventDataResponseTaskShockingNiceCar' - | 'CEventDataResponseTaskShockingPoliceInvestigate' - | 'CEventDataResponseTaskThreat' - | 'CEventDataResponseTaskTurnToFace' - | 'CEventDataResponseTaskWalkAway' - | 'CEventDataResponseTaskWalkRoundEntity' - | 'CEventDataResponseTaskWalkRoundFire' - | 'CEventDeadPedFound' - | 'CEventDeath' - | 'CEventDecisionMakerResponse' - | 'CEventDisturbance' - | 'CEventDraggedOutCar' - | 'CEventEditableResponse' - | 'CEventEncroachingPed' - | 'CEventEntityDamaged' - | 'CEventEntityDestroyed' - | 'CEventExplosion' - | 'CEventExplosionHeard' - | 'CEventFireNearby' - | 'CEventFootStepHeard' - | 'CEventFriendlyAimedAt' - | 'CEventFriendlyFireNearMiss' - | 'CEventGetOutOfWater' - | 'CEventGivePedTask' - | 'CEventGroupScriptAI' - | 'CEventGroupScriptNetwork' - | 'CEventGunAimedAt' - | 'CEventGunShot' - | 'CEventGunShotBulletImpact' - | 'CEventGunShotWhizzedBy' - | 'CEventHelpAmbientFriend' - | 'CEventHurtTransition' - | 'CEventInAir' - | 'CEventInfo' - | 'CEventInfoBase' - | 'CEventInjuredCryForHelp' - | 'CEventLeaderEnteredCarAsDriver' - | 'CEventLeaderExitedCarAsDriver' - | 'CEventLeaderHolsteredWeapon' - | 'CEventLeaderLeftCover' - | 'CEventLeaderUnholsteredWeapon' - | 'CEventMeleeAction' - | 'CEventMustLeaveBoat' - | 'CEventNetworkAdminInvited' - | 'CEventNetworkAttemptHostMigration' - | 'CEventNetworkBail' - | 'CEventNetworkCashTransactionLog' - | 'CEventNetworkCheatTriggered' - | 'CEventNetworkClanInviteReceived' - | 'CEventNetworkClanJoined' - | 'CEventNetworkClanKicked' - | 'CEventNetworkClanLeft' - | 'CEventNetworkClanRankChanged' - | 'CEventNetworkCloudEvent' - | 'CEventNetworkCloudFileResponse' - | 'CEventNetworkEmailReceivedEvent' - | 'CEventNetworkEndMatch' - | 'CEventNetworkEndSession' - | 'CEventNetworkEntityDamage' - | 'CEventNetworkFindSession' - | 'CEventNetworkFollowInviteReceived' - | 'CEventNetworkHostMigration' - | 'CEventNetworkHostSession' - | 'CEventNetworkIncrementStat' - | 'CEventNetworkInviteAccepted' - | 'CEventNetworkInviteConfirmed' - | 'CEventNetworkInviteRejected' - | 'CEventNetworkJoinSession' - | 'CEventNetworkJoinSessionResponse' - | 'CEventNetworkOnlinePermissionsUpdated' - | 'CEventNetworkPedLeftBehind' - | 'CEventNetworkPickupRespawned' - | 'CEventNetworkPlayerArrest' - | 'CEventNetworkPlayerCollectedAmbientPickup' - | 'CEventNetworkPlayerCollectedPickup' - | 'CEventNetworkPlayerCollectedPortablePickup' - | 'CEventNetworkPlayerDroppedPortablePickup' - | 'CEventNetworkPlayerEnteredVehicle' - | 'CEventNetworkPlayerJoinScript' - | 'CEventNetworkPlayerLeftScript' - | 'CEventNetworkPlayerScript' - | 'CEventNetworkPlayerSession' - | 'CEventNetworkPlayerSpawn' - | 'CEventNetworkPresenceInvite' - | 'CEventNetworkPresenceInviteRemoved' - | 'CEventNetworkPresenceInviteReply' - | 'CEventNetworkPresenceTriggerEvent' - | 'CEventNetworkPresence_StatUpdate' - | 'CEventNetworkPrimaryClanChanged' - | 'CEventNetworkRequestDelay' - | 'CEventNetworkRosChanged' - | 'CEventNetworkScAdminPlayerUpdated' - | 'CEventNetworkScAdminReceivedCash' - | 'CEventNetworkScriptEvent' - | 'CEventNetworkSessionEvent' - | 'CEventNetworkShopTransaction' - | 'CEventNetworkSignInStateChanged' - | 'CEventNetworkSocialClubAccountLinked' - | 'CEventNetworkSpectateLocal' - | 'CEventNetworkStartMatch' - | 'CEventNetworkStartSession' - | 'CEventNetworkStorePlayerLeft' - | 'CEventNetworkSummon' - | 'CEventNetworkSystemServiceEvent' - | 'CEventNetworkTextMessageReceived' - | 'CEventNetworkTimedExplosion' - | 'CEventNetworkTransitionEvent' - | 'CEventNetworkTransitionGamerInstruction' - | 'CEventNetworkTransitionMemberJoined' - | 'CEventNetworkTransitionMemberLeft' - | 'CEventNetworkTransitionParameterChanged' - | 'CEventNetworkTransitionStarted' - | 'CEventNetworkTransitionStringChanged' - | 'CEventNetworkVehicleUndrivable' - | 'CEventNetworkVoiceConnectionRequested' - | 'CEventNetworkVoiceConnectionResponse' - | 'CEventNetworkVoiceConnectionTerminated' - | 'CEventNetworkVoiceSessionEnded' - | 'CEventNetworkVoiceSessionStarted' - | 'CEventNetworkWithData' - | 'CEventNetwork_InboxMsgReceived' - | 'CEventNewTask' - | 'CEventObjectCollision' - | 'CEventOnFire' - | 'CEventOpenDoor' - | 'CEventPedCollisionWithPed' - | 'CEventPedCollisionWithPlayer' - | 'CEventPedEnteredMyVehicle' - | 'CEventPedJackingMyVehicle' - | 'CEventPedOnCarRoof' - | 'CEventPedSeenDeadPed' - | 'CEventPlayerCollisionWithPed' - | 'CEventPlayerDeath' - | 'CEventPlayerUnableToEnterVehicle' - | 'CEventPotentialBeWalkedInto' - | 'CEventPotentialBlast' - | 'CEventPotentialGetRunOver' - | 'CEventPotentialWalkIntoVehicle' - | 'CEventProvidingCover' - | 'CEventRanOverPed' - | 'CEventReactionEnemyPed' - | 'CEventReactionInvestigateDeadPed' - | 'CEventReactionInvestigateThreat' - | 'CEventRequestHelp' - | 'CEventRequestHelpWithConfrontation' - | 'CEventRespondedToThreat' - | 'CEventScanner' - | 'CEventScenarioForceAction' - | 'CEventScriptCommand' - | 'CEventScriptWithData' - | 'CEventShocking' - | 'CEventShockingBicycleCrash' - | 'CEventShockingBicycleOnPavement' - | 'CEventShockingCarAlarm' - | 'CEventShockingCarChase' - | 'CEventShockingCarCrash' - | 'CEventShockingCarOnCar' - | 'CEventShockingCarPileUp' - | 'CEventShockingDangerousAnimal' - | 'CEventShockingDeadBody' - | 'CEventShockingDrivingOnPavement' - | 'CEventShockingEngineRevved' - | 'CEventShockingExplosion' - | 'CEventShockingFire' - | 'CEventShockingGunFight' - | 'CEventShockingGunshotFired' - | 'CEventShockingHelicopterOverhead' - | 'CEventShockingHornSounded' - | 'CEventShockingInDangerousVehicle' - | 'CEventShockingInjuredPed' - | 'CEventShockingMadDriver' - | 'CEventShockingMadDriverBicycle' - | 'CEventShockingMadDriverExtreme' - | 'CEventShockingMugging' - | 'CEventShockingNonViolentWeaponAimedAt' - | 'CEventShockingParachuterOverhead' - | 'CEventShockingPedKnockedIntoByPlayer' - | 'CEventShockingPedRunOver' - | 'CEventShockingPedShot' - | 'CEventShockingPlaneFlyby' - | 'CEventShockingPotentialBlast' - | 'CEventShockingPropertyDamage' - | 'CEventShockingRunningPed' - | 'CEventShockingRunningStampede' - | 'CEventShockingSeenCarStolen' - | 'CEventShockingSeenConfrontation' - | 'CEventShockingSeenGangFight' - | 'CEventShockingSeenInsult' - | 'CEventShockingSeenMeleeAction' - | 'CEventShockingSeenNiceCar' - | 'CEventShockingSeenPedKilled' - | 'CEventShockingSiren' - | 'CEventShockingStudioBomb' - | 'CEventShockingVehicleTowed' - | 'CEventShockingVisibleWeapon' - | 'CEventShockingWeaponThreat' - | 'CEventShockingWeirdPed' - | 'CEventShockingWeirdPedApproaching' - | 'CEventShoutBlockingLos' - | 'CEventShoutTargetPosition' - | 'CEventShovePed' - | 'CEventSoundBase' - | 'CEventStatChangedValue' - | 'CEventStaticCountReachedMax' - | 'CEventStuckInAir' - | 'CEventSuspiciousActivity' - | 'CEventSwitch2NM' - | 'CEventUnidentifiedPed' - | 'CEventVehicleCollision' - | 'CEventVehicleDamage' - | 'CEventVehicleDamageWeapon' - | 'CEventVehicleOnFire' - | 'CEventWrithe' + (typeof NATIVE_CLIENT_NETWORK_EVENTS)[number] From 8029f24e9d2e7b590b997304bf36627bb8344d6f Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:48:31 +0100 Subject: [PATCH 19/33] feat(rpc): rework shared-types + include as mandatory dep --- bun.lock | 6 +- rpc/package.json | 8 +-- rpc/src/core/client.ts | 99 +++++++++++++---------------- rpc/src/core/server.ts | 99 ++++++++++++----------------- rpc/src/core/webview.ts | 77 ++++++++++------------ rpc/src/utils/typing.ts | 47 ++++++++++++++ shared-types/index.d.ts | 58 +++++++++++++++++ shared-types/package.json | 11 +++- shared-types/types/types/index.d.ts | 52 --------------- 9 files changed, 235 insertions(+), 222 deletions(-) create mode 100644 rpc/src/utils/typing.ts create mode 100644 shared-types/index.d.ts delete mode 100644 shared-types/types/types/index.d.ts diff --git a/bun.lock b/bun.lock index 935ca86..681717c 100644 --- a/bun.lock +++ b/bun.lock @@ -12,16 +12,16 @@ "rpc": { "name": "@entityseven/fivem-rpc", "version": "0.1.0", + "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", }, "optionalPeers": [ - "@entityseven/fivem-rpc-shared-types", "typescript", ], }, diff --git a/rpc/package.json b/rpc/package.json index 89bc1ad..6c40f22 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -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 } diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 244076a..660cf1c 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -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, - CallbackReturn extends ReturnType, - >( + public onServer>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterServer, 'onServer', eventName, cb) } - public offServer( + public offServer>( eventName: EventName, ): this { return this.unlisten(this._emitterServer, 'offServer', eventName) } public async emitServer< - EventName extends keyof s.RPCEvents_ClientServer, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { + EventName extends RPCEventName, + >( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { const payload = this.request(eventName, 'server', args, null) emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload)) - return this._pending.wait>(payload) + return this._pending.wait(payload) } // ===== WEBVIEW ===== - public onWebview< - EventName extends keyof s.RPCEvents_WebviewClient, - CallbackArguments extends Parameters, - CallbackReturn extends ReturnType, - >( + public onWebview>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } - public offWebview( + public offWebview>( eventName: EventName, ): this { return this.unlisten(this._emitterWeb, 'offWebview', eventName) } public async emitWebview< - EventName extends keyof s.RPCEvents_ClientWebview, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { + EventName extends RPCEventName, + >( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { 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>(payload) + return this._pending.wait(payload) } // ===== SELF ===== - public onSelf< - EventName extends keyof s.RPCEvents_Client, - CallbackArguments extends Parameters, - CallbackReturn extends ReturnType, - >( + public onSelf>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } - public offSelf( + public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } - public async emitSelf< - EventName extends keyof s.RPCEvents_Client, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { - return this.emitLocal>(eventName, args) + public async emitSelf>( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { + return this.emitLocal(eventName, args) } // ===== OTHER ===== - public onCommand< - CommandName extends s.RPCCommands_Client, - CallbackArguments extends unknown[], - >( + public onCommand>( 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, - >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { + public onNativeEvent( + eventName: EventName, + cb: (...args: Parameters) => 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) => void, + ): this { if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) { throw new RPCError( RPCErrors.UNKNOWN_NATIVE, diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 0e3d76e..1f86baf 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -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, - CallbackReturn extends ReturnType, - >( + public onClient>( eventName: EventName, - cb: ( - player: number, - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterClient, 'onClient', eventName, cb) } - public offClient( + public offClient>( eventName: EventName, ): this { return this.unlisten(this._emitterClient, 'offClient', eventName) } public async emitClient< - EventName extends keyof s.RPCEvents_ServerClient, - Arguments extends Parameters, - Response extends ReturnType, + EventName extends RPCEventName, >( player: number, eventName: EventName, - ...args: Arguments - ): Promise> { + ...args: RPCEventArgs + ): Promise> { const payload = this.request(eventName, 'client', args, player) emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload, player) + return this._pending.wait(payload, player) } public async emitClientEveryone< - EventName extends keyof s.RPCEvents_ServerClient, - Arguments extends Parameters, - >(eventName: EventName, ...args: Arguments): Promise { + EventName extends RPCEventName, + >( + eventName: EventName, + ...args: RPCEventArgs + ): Promise { 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, - CallbackReturn extends ReturnType, - >( + public onWebview>( eventName: EventName, - cb: ( - player: number, - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } - public offWebview( + public offWebview>( eventName: EventName, ): this { return this.unlisten(this._emitterWeb, 'offWebview', eventName) } public async emitWebview< - EventName extends keyof s.RPCEvents_ServerWebview, - Arguments extends Parameters, - Response extends ReturnType, + EventName extends RPCEventName, >( player: number, eventName: EventName, - ...args: Arguments - ): Promise> { + ...args: RPCEventArgs + ): Promise> { const payload = this.request(eventName, 'webview', args, player) emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload)) - return this._pending.wait>(payload, player) + return this._pending.wait(payload, player) } // ===== SELF ===== - public onSelf< - EventName extends keyof s.RPCEvents_Server, - CallbackArguments extends Parameters, - CallbackReturn extends ReturnType, - >( + public onSelf>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } - public offSelf( + public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } - public async emitSelf< - EventName extends keyof s.RPCEvents_Server, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { - return this.emitLocal>(eventName, args) + public async emitSelf>( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { + return this.emitLocal(eventName, args) } // ===== OTHER ===== - public onCommand< - CommandName extends s.RPCCommands_Server, - CallbackArguments extends unknown[], - >( + public onCommand>( 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, - >(eventName: EventName, cb: (...args: CallbackArguments) => void): this { + public onNativeEvent( + eventName: EventName, + cb: (...args: Parameters) => void, + ): this { if (!NATIVE_SERVER_EVENTS.includes(eventName)) { throw new RPCError( RPCErrors.UNKNOWN_NATIVE, diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 10ebeb2..ec5e4e1 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -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, - CallbackReturn extends ReturnType, - >( + public onClient>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterClient, 'onClient', eventName, cb) } - public offClient( + public offClient>( eventName: EventName, ): this { return this.unlisten(this._emitterClient, 'offClient', eventName) } public async emitClient< - EventName extends keyof s.RPCEvents_WebviewClient, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { + EventName extends RPCEventName, + >( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { const payload = this.request(eventName, 'client', args, null) - return this._request>(payload) + return this._request(payload) } // ===== SERVER ===== - public onServer< - EventName extends keyof s.RPCEvents_ServerWebview, - CallbackArguments extends Parameters, - CallbackReturn extends ReturnType, - >( + public onServer>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterServer, 'onServer', eventName, cb) } - public offServer( + public offServer>( eventName: EventName, - ): RPCInstanceWebview { + ): this { return this.unlisten(this._emitterServer, 'offServer', eventName) } public async emitServer< - EventName extends keyof s.RPCEvents_WebviewServer, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { + EventName extends RPCEventName, + >( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { const payload = this.request(eventName, 'server', args, null) - return this._request>(payload) + return this._request(payload) } // ===== SELF ===== - public onSelf< - EventName extends keyof s.RPCEvents_Webview, - CallbackArguments extends Parameters, - CallbackReturn extends ReturnType, - >( + public onSelf>( eventName: EventName, - cb: ( - ...args: CallbackArguments - ) => Awaited | Promise>, + cb: RPCListener, ): this { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } - public offSelf( + public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } - public async emitSelf< - EventName extends keyof s.RPCEvents_Webview, - Arguments extends Parameters, - Response extends ReturnType, - >(eventName: EventName, ...args: Arguments): Promise> { - return this.emitLocal>(eventName, args) + public async emitSelf>( + eventName: EventName, + ...args: RPCEventArgs + ): Promise> { + return this.emitLocal(eventName, args) } // ===== UTILS ===== diff --git a/rpc/src/utils/typing.ts b/rpc/src/utils/typing.ts new file mode 100644 index 0000000..1dc306e --- /dev/null +++ b/rpc/src/utils/typing.ts @@ -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 = 0 extends 1 & T ? true : false + +/** The declared map, or a loose one when nothing is declared */ +type EventMap = + IsAny extends true + ? Record + : [keyof T] extends [never] + ? Record + : T + +export type RPCEventName = keyof EventMap & string + +export type RPCEventArgs< + T, + K extends RPCEventName, +> = EventMap[K] extends (...args: infer A extends unknown[]) => unknown + ? A + : never + +export type RPCEventResult< + T, + K extends RPCEventName, +> = EventMap[K] extends (...args: never[]) => infer R ? Awaited : never + +/** Listener for event `K` of map `T`; `Prefix` goes before the event arguments */ +export type RPCListener< + T, + K extends RPCEventName, + Prefix extends unknown[] = [], +> = ( + ...args: [...Prefix, ...RPCEventArgs] +) => RPCEventResult | Promise> + +export type RPCCommandName = [keyof T] extends [never] + ? string + : keyof T & string diff --git a/shared-types/index.d.ts b/shared-types/index.d.ts new file mode 100644 index 0000000..549d315 --- /dev/null +++ b/shared-types/index.d.ts @@ -0,0 +1,58 @@ +/** + * Event and command declarations for `@entityseven/fivem-rpc`. + * + * Every interface starts empty: until you declare something in it, the matching + * rpc methods accept any name and any arguments. Declare your own by augmenting + * this module from any `.d.ts` (or `.ts`) file included in your project: + * + * @example + * // shared/rpc.d.ts + * import '@entityseven/fivem-rpc-shared-types' + * + * declare module '@entityseven/fivem-rpc-shared-types' { + * interface RPCEvents_ClientServer { + * // name(arguments): value returned by the listener + * buyItem(item: string, amount: number): boolean + * } + * interface RPCCommands_Server { + * ban: true + * } + * } + */ + +// ===== COMMANDS (names are the keys, values are not used) ===== + +/** Commands registered with `rpc.onCommand` on the client */ +export interface RPCCommands_Client {} + +/** Commands registered with `rpc.onCommand` on the server */ +export interface RPCCommands_Server {} + +// ===== EVENTS (caller -> receiver) ===== + +/** Client -> client: `emitSelf` / `onSelf` on the client */ +export interface RPCEvents_Client {} + +/** Client -> server: `emitServer` on the client, `onClient` on the server */ +export interface RPCEvents_ClientServer {} + +/** Client -> webview: `emitWebview` on the client, `onClient` on the webview */ +export interface RPCEvents_ClientWebview {} + +/** Server -> server: `emitSelf` / `onSelf` on the server */ +export interface RPCEvents_Server {} + +/** Server -> client: `emitClient` on the server, `onServer` on the client */ +export interface RPCEvents_ServerClient {} + +/** Server -> webview: `emitWebview` on the server, `onServer` on the webview */ +export interface RPCEvents_ServerWebview {} + +/** Webview -> webview: `emitSelf` / `onSelf` on the webview */ +export interface RPCEvents_Webview {} + +/** Webview -> client: `emitClient` on the webview, `onWebview` on the client */ +export interface RPCEvents_WebviewClient {} + +/** Webview -> server: `emitServer` on the webview, `onWebview` on the server */ +export interface RPCEvents_WebviewServer {} diff --git a/shared-types/package.json b/shared-types/package.json index 3027291..cada3f2 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -28,12 +28,17 @@ "directory": "shared-types" }, "files": [ - "types/**/*", + "index.d.ts", "readme.md", "license.md" ], - "type": "module", - "types": "types/types/index.d.ts", + "types": "./index.d.ts", + "exports": { + ".": { + "types": "./index.d.ts" + }, + "./package.json": "./package.json" + }, "peerDependencies": { "typescript": ">=5" }, diff --git a/shared-types/types/types/index.d.ts b/shared-types/types/types/index.d.ts deleted file mode 100644 index 4c2634d..0000000 --- a/shared-types/types/types/index.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -declare module '@entityseven/fivem-rpc-shared-types' { - // Client commands names - export type RPCCommands_Client = '' - - // Server commands names - export type RPCCommands_Server = '' - - // Client -> Client events - export interface RPCEvents_Client { - _(): void - } - - // Client -> Server events - export interface RPCEvents_ClientServer { - _(): void - } - - // Client -> Webview events - export interface RPCEvents_ClientWebview { - _(): void - } - - // Server -> Server events - export interface RPCEvents_Server { - _(): void - } - - // Server -> Client events - export interface RPCEvents_ServerClient { - _(): void - } - - // Server -> Server events - export interface RPCEvents_ServerWebview { - _(): void - } - - // Webview -> Webview events - export interface RPCEvents_Webview { - _(): void - } - - // Webview -> Client events - export interface RPCEvents_WebviewClient { - _(): void - } - - // Webview -> Server events - export interface RPCEvents_WebviewServer { - _(): void - } -} From 50b96eeb80064211c73616417b4db6b837a522aa Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 00:52:07 +0100 Subject: [PATCH 20/33] feat(rpc): change redundant init factory class to function --- rpc/src/index.ts | 102 ++++++++++++++++++---------------------- rpc/src/utils/errors.ts | 5 ++ rpc/src/utils/types.ts | 16 ++++--- 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/rpc/src/index.ts b/rpc/src/index.ts index 82e7c17..180f773 100644 --- a/rpc/src/index.ts +++ b/rpc/src/index.ts @@ -1,7 +1,7 @@ import { RPCInstanceClient } from './core/client' import { RPCInstanceServer } from './core/server' import { RPCInstanceWebview } from './core/webview' -import { RPCError } from './utils/errors' +import { RPCError, unknownEnvironmentMessage } from './utils/errors' import { type RPCConfig, type RPCEnvironment, @@ -10,64 +10,56 @@ import { } from './utils/types' /** - * RPC Factory + * Creates the RPC instance for one environment. Create exactly one per + * environment (server, client, webview) and export it from a local module. + * + * @throws {@link RPCError} `UNKNOWN_ENVIRONMENT` if `config.env` is not + * `'server'`, `'client'` or `'webview'` * * @example - * // returns RPCInstanceServer - * const rpc = new RPCFactory({ env: "server" }).get() - * - * @example - * // returns RPCInstanceClient - * const rpc = new RPCFactory({ env: "client" }).get() - * - * @example - * // returns RPCInstanceWebview - * const rpc = new RPCFactory({ env: "webview" }).get() - * - * @class + * // server/rpc.ts + * import { createRPC } from '@entityseven/fivem-rpc' + * export const rpc = createRPC({ env: 'server' }) */ -class RPCFactory { - private readonly operator: - | RPCInstanceServer - | RPCInstanceClient - | RPCInstanceWebview - - /** - * Instance options - * @param {object} opts - Options - * @param {string} opts.env - Instance environment - * @param {boolean} opts.debug - Show additional logs - */ - constructor(opts: RPCConfig) { - console.log('[RPC] Initializing...') - - switch (opts.env) { - case 'server': - this.operator = new RPCInstanceServer(opts as RPCConfig<'server'>) - break - case 'client': - this.operator = new RPCInstanceClient(opts as RPCConfig<'client'>) - break - case 'webview': - this.operator = new RPCInstanceWebview(opts as RPCConfig<'webview'>) - break - default: - throw new RPCError( - RPCErrors.UNKNOWN_ENVIRONMENT, - RPCErrors.UNKNOWN_ENVIRONMENT, - ) - } - } - - public get(): RPCEnvironmentResolved { - return this.operator as RPCEnvironmentResolved +export function createRPC( + config: RPCConfig, +): RPCEnvironmentResolved { + switch (config.env) { + case 'server': + return new RPCInstanceServer( + config as RPCConfig<'server'>, + ) as RPCEnvironmentResolved + case 'client': + return new RPCInstanceClient( + config as RPCConfig<'client'>, + ) as RPCEnvironmentResolved + case 'webview': + return new RPCInstanceWebview( + config as RPCConfig<'webview'>, + ) as RPCEnvironmentResolved + default: + throw new RPCError( + RPCErrors.UNKNOWN_ENVIRONMENT, + unknownEnvironmentMessage(config.env), + ) } } -export { RPCFactory } -export * from './utils/types' +export type { RPCInstanceClient } from './core/client' +export type { RPCInstanceServer } from './core/server' +export type { RPCInstanceWebview } from './core/webview' export { RPCError, type RPCErrorDetails } from './utils/errors' -export * from './utils/native' -export type * from './core/server' -export type * from './core/client' -export type * from './core/webview' +export { + NATIVE_CLIENT_EVENTS, + NATIVE_CLIENT_NETWORK_EVENTS, + NATIVE_SERVER_EVENTS, +} from './utils/native' +export { + type RPCConfig, + type RPCEnvironment, + RPCErrors, + type RPCNativeClientEvents, + type RPCNativeClientNetworkEvents, + type RPCNativeClientNetworkEventsNames, + type RPCNativeServerEvents, +} from './utils/types' diff --git a/rpc/src/utils/errors.ts b/rpc/src/utils/errors.ts index ab4eb05..73389f1 100644 --- a/rpc/src/utils/errors.ts +++ b/rpc/src/utils/errors.ts @@ -66,6 +66,11 @@ export function notRegisteredMessage( return `No listener for "${event}" on ${receiver}. Register it with rpc.${listenerName(receiver, sender)}("${event}", ...) in ${receiver} code.` } +/** **Internal** */ +export function unknownEnvironmentMessage(env: unknown): string { + return `Unknown env ${JSON.stringify(env)}. Use createRPC({ env: 'server' }), 'client' or 'webview'.` +} + /** **Internal** */ export function unknownNativeMessage(event: string, list: string): string { return `"${event}" is not in ${list}. Events not listed there can be registered with FiveM's on("${event}", ...) directly.` diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index 221375a..b42cecb 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -17,13 +17,15 @@ export type RPCEnvironmentResolved = ? RPCInstanceWebview : never -/** - * `RPCFactory` config. - * - * If environment does not match will throw `RPCErrors.UNKNOWN_ENVIRONMENT` - */ -export type RPCConfig = { +/** `createRPC` config */ +export type RPCConfig = { + /** Environment this instance runs in */ env: T + /** + * Log every registration, call and incoming payload + * + * @defaultValue false + */ debug?: boolean /** * Milliseconds to wait for a response before the call rejects with @@ -103,7 +105,7 @@ export enum RPCEvents { export enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', UNKNOWN_NATIVE = 'Unknown native event', - UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', + UNKNOWN_ENVIRONMENT = 'Unknown environment', TIMEOUT = 'Timed out waiting for response', HANDLER_ERROR = 'Listener threw an error', } From 79fe874c902dc9839ddb9abecacfd194a0b325fb Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:08:09 +0100 Subject: [PATCH 21/33] fix(rpc): remove dead RPCStateRaw branch --- rpc/src/core/webview.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index ec5e4e1..3ed04af 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -6,7 +6,6 @@ import { RPCEvents, type RPCConfig, type RPCState, - type RPCStateRaw, type RPCStateWeb, } from '../utils/types' import type { @@ -157,16 +156,13 @@ export class RPCInstanceWebview extends RPCInstanceBase { return response } - private async _createHttpClientRequest( - data: RPCStateRaw | RPCState, - ): Promise { - const dataRaw = typeof data === 'string' ? data : stringify(data) + private async _createHttpClientRequest(data: RPCState): Promise { const options = { method: 'post', headers: { 'Content-Type': 'application/json; charset=UTF-8', }, - body: dataRaw, + body: stringify(data), } const resourceName = window?.GetParentResourceName?.() ?? 'nui-frame-app' return fetch( From 143bee3e4e9cab6c4baa8426168844db1c3ff3c5 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:12:44 +0100 Subject: [PATCH 22/33] chore(rpc): drop dead code --- rpc/src/utils/funcs.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rpc/src/utils/funcs.ts b/rpc/src/utils/funcs.ts index daf5e67..977057d 100644 --- a/rpc/src/utils/funcs.ts +++ b/rpc/src/utils/funcs.ts @@ -54,15 +54,11 @@ export function stringify(data: RPCState): RPCStateRaw { return JSON.stringify(data) as RPCStateRaw } -// automatically parsed by FiveM -// export function parseWeb(data: RPCStateWebRaw): RPCStateWeb { -// return JSON.parse(data) -// } - /** * **Internal** * - * Typed data serializer + * Typed data serializer. No parse counterpart: the webview receives NUI + * messages already parsed by FiveM. */ export function stringifyWeb(data: RPCStateWeb): RPCStateWebRaw { return JSON.stringify(data) as RPCStateWebRaw From ed66e61de028099bbc985390704a70928dc729ff Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:16:23 +0100 Subject: [PATCH 23/33] chore(rpc): add explanation comments --- rpc/src/core/webview.ts | 2 ++ rpc/src/utils/funcs.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 3ed04af..17836e1 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -164,6 +164,8 @@ export class RPCInstanceWebview extends RPCInstanceBase { }, body: stringify(data), } + // FiveM injects GetParentResourceName into NUI pages. Without it (page + // opened in a regular browser) the fetch fails and the call rejects. const resourceName = window?.GetParentResourceName?.() ?? 'nui-frame-app' return fetch( `https://${resourceName}/${RPCEvents.LISTENER_WEB}`, diff --git a/rpc/src/utils/funcs.ts b/rpc/src/utils/funcs.ts index 977057d..f67646d 100644 --- a/rpc/src/utils/funcs.ts +++ b/rpc/src/utils/funcs.ts @@ -64,7 +64,14 @@ export function stringifyWeb(data: RPCStateWeb): RPCStateWebRaw { return JSON.stringify(data) as RPCStateWebRaw } -/** **Internal** */ +/** + * **Internal** + * + * UUID v4 shaped call id. Not `crypto.randomUUID`: the FiveM client runtime + * has no `crypto` global, and one generator serves all environments. Only + * pairs a response with its call, so it need not be unguessable: the server + * binds each call to its target player (`Pending` peer check). + */ export function generateUUID(): string { let uuid = '' let random = 0 From d1896516896c20df49734da2b353c33bea71bc08 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:22:14 +0100 Subject: [PATCH 24/33] feat: enforce stricter repo-wide typecheck --- .oxlintrc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 7fe84d1..ac7f8d8 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -6,8 +6,8 @@ "suspicious": "warn" }, "rules": { - "typescript/no-explicit-any": "warn", - "typescript/no-unsafe-function-type": "warn", + "typescript/no-explicit-any": "error", + "typescript/no-unsafe-function-type": "error", "eslint/no-underscore-dangle": "off" }, "ignorePatterns": ["**/dist"] From 269269771d0abde0f107b21944eb9434827cbdcd Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:26:03 +0100 Subject: [PATCH 25/33] docs: bump readme.md --- readme.md | 63 ++++++++++++++++++++++++++++++------------ rpc/readme.md | 50 ++++----------------------------- shared-types/readme.md | 14 ++-------- 3 files changed, 52 insertions(+), 75 deletions(-) diff --git a/readme.md b/readme.md index ae6997d..6af4fe4 100644 --- a/readme.md +++ b/readme.md @@ -2,37 +2,64 @@ is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS +## Motivation + +The idea was to create an extensible package, with various features to simplify the development process and provide as much comfort as possible. Inspired by usage of [altv-xrpc](https://github.com/xxshady/altv-xrpc) + +## Packages + +| Package | Docs | +| ----------------------------------------------------- | -------------------------------------- | +| [`@entityseven/fivem-rpc`](rpc) | [API reference](rpc/readme.md) | +| [`@entityseven/fivem-rpc-shared-types`](shared-types) | [Typing setup](shared-types/readme.md) | + ## Installation ```bash - pnpm i @entityseven/fivem-rpc +npm i @entityseven/fivem-rpc +pnpm add @entityseven/fivem-rpc +yarn add @entityseven/fivem-rpc +bun add @entityseven/fivem-rpc ``` +Optional, for typed event names, arguments and results ([typing setup](shared-types/readme.md)): + ```bash - yarn add @entityseven/fivem-rpc +npm i -D @entityseven/fivem-rpc-shared-types +pnpm add -D @entityseven/fivem-rpc-shared-types +yarn add -D @entityseven/fivem-rpc-shared-types +bun add -d @entityseven/fivem-rpc-shared-types ``` -```bash - bun add @entityseven/fivem-rpc +## Quick start + +Create exactly one instance per environment and import it from your own module, not from the library. The client needs one even if it only relays between server and webview. + +```ts +// server/rpc.ts +import { createRPC } from '@entityseven/fivem-rpc' +export const rpc = createRPC({ env: 'server' }) + +// client/rpc.ts +import { createRPC } from '@entityseven/fivem-rpc' +export const rpc = createRPC({ env: 'client' }) + +// webview/rpc.ts +import { createRPC } from '@entityseven/fivem-rpc' +export const rpc = createRPC({ env: 'webview' }) ``` -It is highly recommended to also install additional package for enhanced typing +Listen on one side, emit from the other and await the listener's return value: -```bash - pnpm i @entityseven/fivem-rpc-shared-types -D +```ts +// server +rpc.onClient('ping', (player, message) => `pong: ${message} (from ${player})`) + +// client +const reply = await rpc.emitServer('ping', 'hello') ``` -```bash - yarn add @entityseven/fivem-rpc-shared-types --dev -``` - -```bash - bun add @entityseven/fivem-rpc-shared-types -d -``` - -## Docs - -Can be found in [/rpc/readme.md](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/readme.md) +All methods: [API reference](rpc/readme.md). ## Features diff --git a/rpc/readme.md b/rpc/readme.md index bdc7f99..5cd48fb 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -1,48 +1,8 @@ # FiveM RPC -is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS. [Extra info](https://github.com/rilaxik/fivem-rpc/blob/master/readme.md) +is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS -# Motivation - -The idea was to create an extensible package, with various features to simplify the development process and provide as much comfort as possible. Inspired by usage of [altv-xrpc](https://github.com/xxshady/altv-xrpc) - -# Installation - -```bash - pnpm i @entityseven/fivem-rpc -``` - -```bash - yarn add @entityseven/fivem-rpc -``` - -```bash - bun add @entityseven/fivem-rpc -``` - -It is highly recommended to also install additional package for enhanced typing - -```bash - pnpm i @entityseven/fivem-rpc-shared-types -D -``` - -```bash - yarn add @entityseven/fivem-rpc-shared-types --dev -``` - -```bash - bun add @entityseven/fivem-rpc-shared-types -d -``` - -## Usage - -FiveM RPC is meant to be a singletone per environment. This means you _must create only one_ `RPCFactory` per your server/client/web. This also enables modifying `const rpc` to your needs, adding new methods or variables by forcing you to import it from file instead of library reference - -```ts -// lib/rpc.ts -import { RPCFactory } from '@entityseven/fivem-rpc' -export const rpc = new RPCFactory(/* options */).get() -``` +Installation, quick start and package overview: [main readme](../readme.md). Typed events: [shared-types](../shared-types/readme.md). # Docs @@ -91,7 +51,7 @@ Type: 'event' Data: [] ``` -## Server ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/server.ts)) +## Server ([source](src/core/server.ts)) ### onClient @@ -205,7 +165,7 @@ rpc.onNativeEvent('playerJoining', (source, oldId) => { }) ``` -## Client ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/client.ts)) +## Client ([source](src/core/client.ts)) ### onServer @@ -329,7 +289,7 @@ Sets or removes focus and cursor from own webview rpc.setWebviewFocus(true /* focus */, true /* show cursor */) ``` -## Webview ([source](https://github.com/rilaxik/fivem-rpc/blob/master/rpc/src/core/webview.ts)) +## Webview ([source](src/core/webview.ts)) ### onClient diff --git a/shared-types/readme.md b/shared-types/readme.md index 31f08bf..8287670 100644 --- a/shared-types/readme.md +++ b/shared-types/readme.md @@ -1,20 +1,10 @@ # FiveM RPC Shared Types -### [Docs & Info](https://github.com/rilaxik/fivem-rpc/blob/master/readme.md) +### [Docs & Info](../readme.md) ## Installation -```bash - pnpm i @entityseven/fivem-rpc-shared-types -D -``` - -```bash - yarn add @entityseven/fivem-rpc-shared-types --dev -``` - -```bash - bun add @entityseven/fivem-rpc-shared-types -d -``` +See the [main readme](../readme.md#installation). ## Usage From 674eb25000cf7e801f9dd4ef7e57ccee5c441c1f Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:29:06 +0100 Subject: [PATCH 26/33] docs: bump readme.md --- rpc/readme.md | 76 +++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/rpc/readme.md b/rpc/readme.md index 5cd48fb..b477405 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -1,54 +1,58 @@ # FiveM RPC -is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS +is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS Installation, quick start and package overview: [main readme](../readme.md). Typed events: [shared-types](../shared-types/readme.md). -# Docs +## Exports -## Extras +Besides `createRPC` the package exports: -Along with `RPCFactory` you can also import all the types used internally, types for native client/server events and lists of native client/server events. All of that is documented in JSDoc, so no need to duplicate it here +- `RPCError`, `RPCErrors`, `RPCErrorDetails` - see [Errors](#errors) +- `RPCConfig`, `RPCEnvironment` and the instance types `RPCInstanceServer`, `RPCInstanceClient`, `RPCInstanceWebview` +- native event types `RPCNativeServerEvents`, `RPCNativeClientEvents`, `RPCNativeClientNetworkEvents`, `RPCNativeClientNetworkEventsNames` and the lists `NATIVE_SERVER_EVENTS`, `NATIVE_CLIENT_EVENTS`, `NATIVE_CLIENT_NETWORK_EVENTS` accepted by the `onNative*` methods ## RPCConfig ```ts -type RPCConfig = { - env: T - debug?: boolean +type RPCConfig = { + env: 'server' | 'client' | 'webview' + debug?: boolean // default false, logs every registration, call and incoming payload + timeout?: number // default 5000, ms to wait for a response, 0 disables it } ``` -Failing to set `env` to provided type will result in `RPCErrors.UNKNOWN_ENVIRONMENT` -`debug` adds additional console logs to events +An unknown `env` makes `createRPC` throw `RPCError` with code `RPCErrors.UNKNOWN_ENVIRONMENT` ## Errors -Known errors could be one of following or an error throw by a callback specifically +Every error from this library is an `RPCError`. `code` is one of `RPCErrors`, `message` says what to fix, and `details` names the call when the error comes from one ```ts enum RPCErrors { EVENT_NOT_REGISTERED = 'Event not registered', - INVALID_DATA = 'Invalid data (possibly broken JSON)', - NO_PLAYER = 'No player (failed to resolve from local index)', - UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)', - UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")', + UNKNOWN_NATIVE = 'Unknown native event', + UNKNOWN_ENVIRONMENT = 'Unknown environment', + TIMEOUT = 'Timed out waiting for response', + HANDLER_ERROR = 'Listener threw an error', } ``` ### Example error -Values wrapped in `<>` always exist, just not relevant for an example. Keep in mind that some errors are thrown in their destination(`To`) point: this example will throw on server +The server has no `onClient('buyItem', ...)` listener, so the call from the client rejects: -``` -Error: No player (failed to resolve from local index) -Event: 'clientServerEvent' -Uuid: -From: 'client' -To: 'server' -Player: -Type: 'event' -Data: [] +```ts +import { RPCError, RPCErrors } from '@entityseven/fivem-rpc' + +try { + await rpc.emitServer('buyItem', 'water') +} catch (e) { + if (e instanceof RPCError && e.code === RPCErrors.EVENT_NOT_REGISTERED) { + e.message // 'No listener for "buyItem" on server. Register it with rpc.onClient("buyItem", ...) in server code.' + e.details // { event: 'buyItem', uuid: '', from: 'client', to: 'server' } + } +} ``` ## Server ([source](src/core/server.ts)) @@ -83,10 +87,10 @@ const response = await rpc.emitClient(playerServerId, 'serverClientEvent', someD ### emitClientEveryone -Sends event to all clients +Sends event to all clients. One-way: clients run their listener but do not answer ```ts -rpc.emitClientEveryone('serverClientEvent', someData) +await rpc.emitClientEveryone('serverClientEvent', someData) ``` ### onWebview @@ -110,7 +114,7 @@ rpc.offWebview('webviewServerEvent') ### emitWebview -Sends event to specified webview +Sends event to the webview of specified player ```ts const response = await rpc.emitWebview(playerServerId, 'serverWebviewEvent', someData) @@ -147,12 +151,12 @@ const response = await rpc.emitSelf('serverEvent', someData) ### onCommand -Registers chat command. Since arguments are untyped you must validate them yourself +Registers chat command. `args` are the raw strings typed after the command, validate them yourself. With `restricted` set to `true` only players with the ACE permission `command.` can use it (defaults to `false`) ```ts -rpc.onCommand('serverCommand', (player, args, commandRaw) => { +rpc.onCommand('serverCommand', (player, args, rawCommand) => { // logic -}) +}, true /* restricted */) ``` ### onNativeEvent @@ -192,7 +196,7 @@ Sends event to server ```ts const response = await rpc.emitServer('clientServerEvent', someData) -// response will come from webview listener with returned data +// response will come from server listener with returned data ``` ### onWebview @@ -216,7 +220,7 @@ rpc.offWebview('webviewClientEvent') ### emitWebview -Sends event to specified webview +Sends event to own webview ```ts const response = await rpc.emitWebview('clientWebviewEvent', someData) @@ -253,10 +257,10 @@ const response = await rpc.emitSelf('clientEvent', someData) ### onCommand -Registers chat command. Since arguments are untyped you must validate them yourself +Registers chat command. `args` are the raw strings typed after the command, validate them yourself ```ts -rpc.onCommand('clientCommand', (player, args, commandRaw) => { +rpc.onCommand('clientCommand', (player, args, rawCommand) => { // logic }) ``` @@ -344,7 +348,7 @@ Sends event to server ```ts const response = await rpc.emitServer('webviewServerEvent', someData) -// response will come from webview listener with returned data +// response will come from server listener with returned data ``` ### onSelf @@ -375,6 +379,6 @@ const response = await rpc.emitSelf('webviewEvent', someData) // response will come from webview listener with returned data ``` -# License +## License Licensed under Custom Attribution-NoDerivs Software License From c4c9d02fd40525867af9fcca0157dbefb8fcf48f Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:30:03 +0100 Subject: [PATCH 27/33] docs: bump readme.md --- rpc/readme.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rpc/readme.md b/rpc/readme.md index b477405..7422187 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -55,6 +55,29 @@ try { } ``` +## How it works + +### Routing + +Server and client talk over FiveM network events, client and webview over NUI messages and NUI callbacks. Webview and server never talk directly: every call between them is relayed by the client of that player. So every client must run `createRPC({ env: 'client' })`, even with no listeners of its own, or those calls time out + +### One listener per event + +Each `on*` method keeps one listener per event name. Registering the same name again replaces the previous listener, `off*` removes it. Directions are separate: `onClient('x')` and `onWebview('x')` on the server do not replace each other + +### Responses, errors and timeouts + +- `emit*` resolves with the value the listener returns (promises are awaited) +- no listener on the target: the call rejects with `RPCErrors.EVENT_NOT_REGISTERED` +- the listener throws: the target logs the error with `console.error`, the call rejects with `RPCErrors.HANDLER_ERROR` and the original message +- no response within `RPCConfig.timeout` (default 5000 ms): the call rejects with `RPCErrors.TIMEOUT` and a late response is ignored. `timeout: 0` waits forever +- `emitSelf` calls the local listener directly, whatever it throws reaches the caller unchanged +- `emitClientEveryone` does not wait for clients: it resolves once sent, failures stay on each client (`console.error` for a throwing listener, the rest with `debug: true`) + +### Player identity + +Server listeners (`onClient`, `onWebview`) get the calling player's server id as the first argument. It comes from FiveM's `source`, never from the payload, so a client cannot pose as another player. Use it instead of player ids passed as arguments. A response to `emitClient` or `emitWebview` is only accepted from the player it was sent to + ## Server ([source](src/core/server.ts)) ### onClient From 40c322e4b7c45878d6b95205d7acca4d67469479 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:38:58 +0100 Subject: [PATCH 28/33] docs: bump readme.md + metadata --- migration.md | 53 ++++++++++ readme.md | 10 +- rpc/package.json | 9 +- rpc/readme.md | 6 +- shared-types/package.json | 7 +- shared-types/readme.md | 216 +++++++++++--------------------------- 6 files changed, 132 insertions(+), 169 deletions(-) create mode 100644 migration.md diff --git a/migration.md b/migration.md new file mode 100644 index 0000000..19c2ec8 --- /dev/null +++ b/migration.md @@ -0,0 +1,53 @@ +# Migrating from 0.1 to 1.0 + +Upgrade server, client and webview together. The payload format changed, so 0.1 and 1.0 cannot talk to each other + +## Creating the instance + +`RPCFactory` is gone, use `createRPC`: + +```ts +// 0.1 +import { RPCFactory } from '@entityseven/fivem-rpc' +export const rpc = new RPCFactory({ env: 'server' }).get() + +// 1.0 +import { createRPC } from '@entityseven/fivem-rpc' +export const rpc = createRPC({ env: 'server' }) +``` + +## Errors and timeouts + +- a failed call now rejects on the caller with `RPCError` (`code`, `message`, `details`). In 0.1 the error was thrown on the receiving side and the call never settled +- every call times out after 5000 ms by default. Change it with `RPCConfig.timeout`, `0` restores the 0.1 behaviour (wait forever) +- `RPCErrors.INVALID_DATA` and `RPCErrors.NO_PLAYER` are removed (invalid payloads are dropped), `RPCErrors.TIMEOUT` and `RPCErrors.HANDLER_ERROR` are new +- the texts of `RPCErrors.UNKNOWN_NATIVE` and `RPCErrors.UNKNOWN_ENVIRONMENT` changed. Compare `error.code` with `RPCErrors`, not message strings + +See [Errors](rpc/readme.md#errors) and [How it works](rpc/readme.md#how-it-works) + +## Typing + +Declarations are now module augmentation of interfaces, the `types` / `typeRoots` tsconfig setup is no longer needed. Follow the [shared-types readme](shared-types/readme.md), then: + +- commands are interface keys instead of string unions: + + ```ts + // 0.1 + export type RPCCommands_Server = 'ban' | 'kick' + + // 1.0 + interface RPCCommands_Server { + ban: true + kick: true + } + ``` + +- remove placeholder members such as `_(): void`. An interface with any member is strict, an empty one accepts everything + +## Other API changes + +- `onCommand` callbacks get `args` as `string[]` (the generic argument type is gone) +- `RPCNativeClientNetworksEvents` is renamed to `RPCNativeClientNetworkEvents` +- internal types are no longer exported: `RPCEnvironmentResolved`, `RPCEventType`, `RPCEvents`, `RPCState`, `RPCStateRaw`, `RPCStateWeb`, `RPCStateWebRaw`. Use `RPCInstanceServer`, `RPCInstanceClient` or `RPCInstanceWebview` for the instance type +- server listeners get the player id from FiveM's `source`. Ids a client puts into the payload are ignored +- `emitClientEveryone` is one-way: clients run their listener but no longer send a response diff --git a/readme.md b/readme.md index 6af4fe4..5abcb51 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # FiveM RPC -is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS +Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong ## Motivation @@ -31,6 +31,8 @@ yarn add -D @entityseven/fivem-rpc-shared-types bun add -d @entityseven/fivem-rpc-shared-types ``` +Upgrading from 0.1: [migration guide](migration.md) + ## Quick start Create exactly one instance per environment and import it from your own module, not from the library. The client needs one even if it only relays between server and webview. @@ -70,11 +72,13 @@ All methods: [API reference](rpc/readme.md). Issues and pull requests are very welcome +Releases are published with `bun publish`, which replaces the `workspace:^` dependency on shared-types with its version (`npm publish` would not) + ## License -Licensed under Custom Attribution-NoDerivs Software License +Licensed under the [Custom Attribution-NoDerivs Software License](license.md) -## WIP +## Roadmap - client observers to catch events between server and webview (subscribe-like behaviour) - client observers to prevent events (middleware-like behaviour) diff --git a/rpc/package.json b/rpc/package.json index 6c40f22..3b1a0c3 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -1,12 +1,15 @@ { "name": "@entityseven/fivem-rpc", "version": "0.1.0", - "description": "FiveM RPC is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS", + "description": "Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong", "keywords": [ + "cfx", "fivem", "fivem-rpc", - "fivem-rpc-shared-types", - "gta" + "gta", + "nui", + "rpc", + "typescript" ], "license": "SEE LICENSE IN license.md", "author": "Entity Seven Group", diff --git a/rpc/readme.md b/rpc/readme.md index 7422187..ebd86f9 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -1,8 +1,8 @@ # FiveM RPC -is an all-in-one package with asynchronous RPC implementation for FiveM servers in JS/TS +Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong -Installation, quick start and package overview: [main readme](../readme.md). Typed events: [shared-types](../shared-types/readme.md). +Installation, quick start and package overview: [main readme](../readme.md). Typed events: [shared-types](../shared-types/readme.md). Upgrading from 0.1: [migration guide](../migration.md). ## Exports @@ -404,4 +404,4 @@ const response = await rpc.emitSelf('webviewEvent', someData) ## License -Licensed under Custom Attribution-NoDerivs Software License +Licensed under the [Custom Attribution-NoDerivs Software License](license.md) diff --git a/shared-types/package.json b/shared-types/package.json index cada3f2..04a85db 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -1,12 +1,13 @@ { "name": "@entityseven/fivem-rpc-shared-types", "version": "0.1.0", - "description": "Shared (enhanced) types for @entityseven/fivem-rpc. Highly recommended to install together", + "description": "Event and command declarations for @entityseven/fivem-rpc, for typed events", "keywords": [ + "cfx", "fivem", "fivem-rpc", - "fivem-rpc-shared-types", - "gta" + "types", + "typescript" ], "license": "SEE LICENSE IN license.md", "author": "Entity Seven Group", diff --git a/shared-types/readme.md b/shared-types/readme.md index 8287670..6061261 100644 --- a/shared-types/readme.md +++ b/shared-types/readme.md @@ -1,173 +1,75 @@ # FiveM RPC Shared Types -### [Docs & Info](../readme.md) +Event and command declarations for [`@entityseven/fivem-rpc`](../rpc/readme.md). With nothing declared, every rpc method accepts any event name, any arguments and any result. Declare your events once and every `on*` and `emit*` call gets checked names, arguments and results ## Installation -See the [main readme](../readme.md#installation). +See the [main readme](../readme.md#installation). The declaration file below imports this package, so it must resolve from that file (in a workspace: install it in the root) ## Usage -This package is an enhanced type support for `@entityseven/fivem-rpc`. It provides ability to strictly type your events for better dx. - -## Example - -This example is neat way to follow up but you can change it as you wish. It will use bun workspaces, pnpm workspaces work in a similar manner. Referring the following folder structure: - -```markdown -apps/ - - server/ - - package.json - - tsconfig.json - - client/ - - package.json - - tsconfig.json - - webview/ - - package.json - - tsconfig.json - - shared/ (this must be available in server, client and webview) - - package.json - - - package.json (root package) - - pnpm-workspace.yaml (only if using pnpm) -``` - -- Environment folder: folder with server, client or webview code in it - -1. Install this package as dev dependency in your root package or in each environment folder separately - - ```markdown - apps/ - - server/ <- (if not installed root) - - client/ <- (if not installed root) - - webview/ <- (if not installed root) - - shared/ - - - package.json <- here - ``` - -2. In `shared/` create folder `fivem-rpc` (or similar), inside it create `index.d.ts` - - ```markdown - apps/ - - server/ - - client/ - - webview/ - - shared/ - - fivem-rpc/ - - index.d.ts <- here - - - package.json - ``` - -3. In `index.d.ts` add following: +1. Create one declaration file shared by server, client and webview code, e.g. `shared/rpc.d.ts`: ```ts + import '@entityseven/fivem-rpc-shared-types' + declare module '@entityseven/fivem-rpc-shared-types' { - // Client commands names - export type RPCCommands_Client = '' - - // Server commands names - export type RPCCommands_Server = '' - - // Client -> Client events - export interface RPCEvents_Client {} - - // Client -> Server events - export interface RPCEvents_ClientServer {} - - // Client -> Webview events - export interface RPCEvents_ClientWebview {} - - // Server -> Server events - export interface RPCEvents_Server {} - - // Server -> Client events - export interface RPCEvents_ServerClient {} - - // Server -> Server events - export interface RPCEvents_ServerWebview {} - - // Webview -> Webview events - export interface RPCEvents_Webview {} - - // Webview -> Client events - export interface RPCEvents_WebviewClient {} - - // Webview -> Server events - export interface RPCEvents_WebviewServer {} - } - ``` - -4. We just created a declaration which will overwrite types from the package. Now we need our packages to refer to these types when linting `rpc` functions. To do this in each environment folder of your project in `tsconfig.json` do these: - ```json5 - { - "compilerOptions": { - "types": [ - "../shared/fivem-rpc/" // or your specific folder - ] + interface RPCEvents_ClientServer { + // event name(arguments): value returned by the listener + buyItem(item: string, amount: number): boolean } - } - ``` -5. We also need to populate interfaces we created in step 3. - -- `RPCCommands_Client` and `RPCCommands_Server` will include your commands names an union strings: - ```ts - export type RPCCommands_Client = 'afk' | 'vanish' | '...' // example names - export type RPCCommands_Server = 'report' | 'ban' | '...' // example names - ``` -- Other interfaces will include your events types. The example will show one but all of the work same way - - ```ts - export interface RPCEvents_ClientServer { - clientToServerEventName(data: string, moreData: boolean): number - "client-to-server-event-name"(data: string, moreData: boolean): number // can also include characters you cannot use as variable or function names - } - ``` - - `clientToServerEventName` or `client-to-server-event-name` is an event name - - `data` and `moreData` are the arguments you need to pass when calling an event and argument you will receive when listening (may also include extra, as player, check type hints) - - `number` is a return type that will be forwarded back to caller - - Doing this will create type hints for you: - - ```ts - // assuming this is in client - const response /* number */ = await rpc.emitServer( - 'clientToServerEventName', /* suggested name */ - 'data', /* will pass typecheck */ - 'moreData', /* will NOT pass typecheck, since required type is `boolean` */ - ) - ``` - -## Example (alternative) - -If previous example does not work or you do not like you can also try it this way. Steps that are not mentioned are the same as previous - -2. In `shared/` create folders `declarations/fivem-rpc`, inside it create `index.d.ts` - - ```markdown - apps/ - - server/ - - client/ - - webview/ - - shared/ - - declarations/ - - fivem-rpc/ - - index.d.ts <- here - - - package.json - ``` - -3. We just created a declaration which will overwrite types from the package. Now we need our packages to refer to these types when linting `rpc` functions. To do this in each environment folder of your project in `tsconfig.json` do these: - ```json5 - { - "compilerOptions": { - "typeRoots": [ - "../shared/declarations/", // or your specific folder - "../../node_modules/@types", // you may also want to add this if some of your other libraries are not showing types now - ] + interface RPCCommands_Server { + ban: true } } ``` -If a any point this stops working for you, do your research on how to redeclare library types and refer to it + The `import` line makes the file a module, so `declare module` adds to the package's interfaces instead of replacing them + +2. Add the file to `include` in the `tsconfig.json` of every environment: + + ```json + { + "include": ["src", "../shared/rpc.d.ts"] + } + ``` + +3. Calls are checked from now on: + + ```ts + // server + rpc.onClient('buyItem', (player, item, amount) => { + // item: string, amount: number, must return boolean + return true + }) + + // client + const bought = await rpc.emitServer('buyItem', 'water', 2) // boolean + await rpc.emitServer('buyItem', 'water') // error: missing `amount` + await rpc.emitServer('buyItme', 'water', 2) // error: unknown event + ``` + +## Interfaces + +Each interface is one direction. An interface you leave empty stays loose (any name, arguments and result), so you can declare them one at a time + +| Interface | Direction | Caller | Listener | +| ------------------------- | ------------------ | ------------------------------------------- | -------------------- | +| `RPCEvents_Client` | client -> client | `emitSelf` (client) | `onSelf` (client) | +| `RPCEvents_ClientServer` | client -> server | `emitServer` (client) | `onClient` (server) | +| `RPCEvents_ClientWebview` | client -> webview | `emitWebview` (client) | `onClient` (webview) | +| `RPCEvents_Server` | server -> server | `emitSelf` (server) | `onSelf` (server) | +| `RPCEvents_ServerClient` | server -> client | `emitClient`, `emitClientEveryone` (server) | `onServer` (client) | +| `RPCEvents_ServerWebview` | server -> webview | `emitWebview` (server) | `onServer` (webview) | +| `RPCEvents_Webview` | webview -> webview | `emitSelf` (webview) | `onSelf` (webview) | +| `RPCEvents_WebviewClient` | webview -> client | `emitClient` (webview) | `onWebview` (client) | +| `RPCEvents_WebviewServer` | webview -> server | `emitServer` (webview) | `onWebview` (server) | +| `RPCCommands_Client` | - | - | `onCommand` (client) | +| `RPCCommands_Server` | - | - | `onCommand` (server) | + +- events: the member name is the event name, its parameters are the arguments, its return type is what `emit*` resolves with. Server listeners get the player id before the declared arguments. Names that are not identifiers work too: `'buy-item'(item: string): boolean` +- commands: the key is the command name, the value is not used (`true`) + +## License + +Licensed under the [Custom Attribution-NoDerivs Software License](license.md) From cca07468d5d1748152a452c584b5df71a93fcd83 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:47:46 +0100 Subject: [PATCH 29/33] docs: TSdoc --- .oxlintrc.json | 12 ++++- rpc/src/core/base.ts | 4 ++ rpc/src/core/client.ts | 111 ++++++++++++++++++++++++++++++++++++++++ rpc/src/core/server.ts | 108 +++++++++++++++++++++++++++++++++++++- rpc/src/core/webview.ts | 75 +++++++++++++++++++++++++++ rpc/src/index.ts | 4 ++ rpc/src/utils/native.ts | 11 +++- rpc/src/utils/types.ts | 20 ++++++-- shared-types/readme.md | 2 +- 9 files changed, 336 insertions(+), 11 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index ac7f8d8..6650c0c 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,6 +1,6 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["eslint", "typescript", "unicorn", "oxc", "import"], + "plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "jsdoc"], "categories": { "correctness": "error", "suspicious": "warn" @@ -8,7 +8,15 @@ "rules": { "typescript/no-explicit-any": "error", "typescript/no-unsafe-function-type": "error", - "eslint/no-underscore-dangle": "off" + "eslint/no-underscore-dangle": "off", + "jsdoc/check-tag-names": [ + "error", + { "typed": true, "definedTags": ["defaultValue", "remarks", "typeParam"] } + ], + "jsdoc/empty-tags": "error", + "jsdoc/no-blank-blocks": "error", + "jsdoc/require-param-description": "error", + "jsdoc/require-param-name": "error" }, "ignorePatterns": ["**/dist"] } diff --git a/rpc/src/core/base.ts b/rpc/src/core/base.ts index 8d15bcc..1f34c94 100644 --- a/rpc/src/core/base.ts +++ b/rpc/src/core/base.ts @@ -14,6 +14,10 @@ import { type RPCStateRaw, } from '../utils/types' +/** + * **Internal** Shared by `RPCInstanceServer`, `RPCInstanceClient` and + * `RPCInstanceWebview`. + */ export class RPCInstanceBase { protected readonly env: RPCEnvironment protected readonly debug: boolean diff --git a/rpc/src/core/client.ts b/rpc/src/core/client.ts index 660cf1c..852a4c9 100644 --- a/rpc/src/core/client.ts +++ b/rpc/src/core/client.ts @@ -26,6 +26,18 @@ import type { } from '../utils/typing' import { RPCInstanceBase } from './base' +/** + * RPC instance for client code, returned by `createRPC({ env: 'client' })`. + * Create one per client and import it from your own module. + * + * - `on*` registers the listener that answers calls from one direction. One + * listener per event: registering the same name again replaces it, `off*` + * removes it + * - `emit*` calls the listener on the target and resolves with its return + * value, or rejects with {@link RPCError} + * - also relays calls between its webview and the server, so every client + * needs an instance even without listeners of its own + */ export class RPCInstanceClient extends RPCInstanceBase { private readonly _emitterServer: Emitter private readonly _emitterWeb: Emitter @@ -123,6 +135,16 @@ export class RPCInstanceClient extends RPCInstanceBase { // ===== SERVER ===== + /** + * Listens for `emitClient` and `emitClientEveryone` calls from the server + * (server -> client). For `emitClientEveryone` the return value is not sent. + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onServer('askTrade', offer => showTradeDialog(offer)) + */ public onServer>( eventName: EventName, cb: RPCListener, @@ -130,12 +152,23 @@ export class RPCInstanceClient extends RPCInstanceBase { return this.listen(this._emitterServer, 'onServer', eventName, cb) } + /** Removes the `onServer` listener for `eventName` */ public offServer>( eventName: EventName, ): this { return this.unlisten(this._emitterServer, 'offServer', eventName) } + /** + * Calls the server's `onClient` listener (client -> server) and resolves + * with its return value. The server listener gets this player's id first. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const money = await rpc.emitServer('getMoney', 'bank') + */ public async emitServer< EventName extends RPCEventName, >( @@ -151,6 +184,16 @@ export class RPCInstanceClient extends RPCInstanceBase { // ===== WEBVIEW ===== + /** + * Listens for `emitClient` calls from this player's webview + * (webview -> client). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onWebview('getPosition', () => GetEntityCoords(PlayerPedId(), false)) + */ public onWebview>( eventName: EventName, cb: RPCListener, @@ -158,12 +201,23 @@ export class RPCInstanceClient extends RPCInstanceBase { return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } + /** Removes the `onWebview` listener for `eventName` */ public offWebview>( eventName: EventName, ): this { return this.unlisten(this._emitterWeb, 'offWebview', eventName) } + /** + * Calls the `onClient` listener in this player's webview (client -> webview) + * and resolves with its return value. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const choice = await rpc.emitWebview('openMenu', items) + */ public async emitWebview< EventName extends RPCEventName, >( @@ -182,6 +236,15 @@ export class RPCInstanceClient extends RPCInstanceBase { // ===== SELF ===== + /** + * Listens for `emitSelf` calls in this environment (client -> client). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onSelf('add', (a, b) => a + b) + */ public onSelf>( eventName: EventName, cb: RPCListener, @@ -189,12 +252,23 @@ export class RPCInstanceClient extends RPCInstanceBase { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } + /** Removes the `onSelf` listener for `eventName` */ public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } + /** + * Calls this environment's own `onSelf` listener directly and resolves with + * its return value. No timeout; errors thrown by the listener reach the caller + * unchanged. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists + * + * @example + * const total = await rpc.emitSelf('add', 2, 3) + */ public async emitSelf>( eventName: EventName, ...args: RPCEventArgs @@ -204,6 +278,15 @@ export class RPCInstanceClient extends RPCInstanceBase { // ===== OTHER ===== + /** + * Registers a chat command (FiveM `RegisterCommand`). + * + * @param cb - gets FiveM's `source`, the strings typed after the command and + * the full command line. Validate `args` yourself + * + * @example + * rpc.onCommand('coords', () => console.log(GetEntityCoords(PlayerPedId(), false))) + */ public onCommand>( command: CommandName, cb: (player: number, args: string[], rawCommand: string) => void, @@ -215,6 +298,15 @@ export class RPCInstanceClient extends RPCInstanceBase { return this } + /** + * Listens to a native FiveM client event, e.g. `entityDamaged`. + * + * @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in + * `NATIVE_CLIENT_EVENTS`. Register other events with FiveM's `on` directly + * + * @example + * rpc.onNativeEvent('entityDamaged', (victim, culprit) => console.log(victim)) + */ public onNativeEvent( eventName: EventName, cb: (...args: Parameters) => void, @@ -233,6 +325,16 @@ export class RPCInstanceClient extends RPCInstanceBase { return this } + /** + * Listens to a native game event, e.g. `CEventShockingCarCrash`. + * + * @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in + * `NATIVE_CLIENT_NETWORK_EVENTS`. Register other events with FiveM's `on` + * directly + * + * @example + * rpc.onNativeNetworkEvent('CEventShockingCarCrash', (entities, eventEntity) => {}) + */ public onNativeNetworkEvent< EventName extends keyof RPCNativeClientNetworkEvents, >( @@ -253,6 +355,15 @@ export class RPCInstanceClient extends RPCInstanceBase { return this } + /** + * Focuses this player's webview (FiveM `SetNuiFocus`). + * + * @param hasFocus - the webview receives keyboard input + * @param hasCursor - the mouse cursor is shown + * + * @example + * rpc.setWebviewFocus(true, true) + */ public setWebviewFocus(hasFocus: boolean, hasCursor: boolean): this { this.log(`setWebviewFocus ${hasFocus} ${hasCursor}`) diff --git a/rpc/src/core/server.ts b/rpc/src/core/server.ts index 1f86baf..7a7d550 100644 --- a/rpc/src/core/server.ts +++ b/rpc/src/core/server.ts @@ -20,6 +20,18 @@ import type { } from '../utils/typing' import { RPCInstanceBase } from './base' +/** + * RPC instance for server code, returned by `createRPC({ env: 'server' })`. + * Create one per server and import it from your own module. + * + * - `on*` registers the listener that answers calls from one direction. One + * listener per event: registering the same name again replaces it, `off*` + * removes it + * - `emit*` calls the listener on the target and resolves with its return + * value, or rejects with {@link RPCError} + * - listeners for client and webview calls get the calling player's server id + * first, taken from FiveM `source` + */ export class RPCInstanceServer extends RPCInstanceBase { private readonly _emitterClient: Emitter private readonly _emitterWeb: Emitter @@ -75,6 +87,16 @@ export class RPCInstanceServer extends RPCInstanceBase { // ===== CLIENT ===== + /** + * Listens for `emitServer` calls from clients (client -> server). + * + * @param cb - gets the calling player's server id first (from FiveM + * `source`, never from the payload), then the event arguments. Its return + * value (awaited) is sent back to the caller + * + * @example + * rpc.onClient('getMoney', (player, account) => getMoney(player, account)) + */ public onClient>( eventName: EventName, cb: RPCListener, @@ -82,12 +104,24 @@ export class RPCInstanceServer extends RPCInstanceBase { return this.listen(this._emitterClient, 'onClient', eventName, cb) } + /** Removes the `onClient` listener for `eventName` */ public offClient>( eventName: EventName, ): this { return this.unlisten(this._emitterClient, 'offClient', eventName) } + /** + * Calls the `onServer` listener on one client (server -> client) and + * resolves with its return value. + * + * @param player - server id of the target player + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const accepted = await rpc.emitClient(player, 'askTrade', offer) + */ public async emitClient< EventName extends RPCEventName, >( @@ -102,6 +136,14 @@ export class RPCInstanceServer extends RPCInstanceBase { return this._pending.wait(payload, player) } + /** + * Runs the `onServer` listener on every client (server -> all clients). + * One-way: resolves once sent, clients do not answer and their failures stay + * on the client. + * + * @example + * await rpc.emitClientEveryone('weatherChanged', 'RAIN') + */ public async emitClientEveryone< EventName extends RPCEventName, >( @@ -115,6 +157,17 @@ export class RPCInstanceServer extends RPCInstanceBase { // ===== WEBVIEW ===== + /** + * Listens for `emitServer` calls from webviews (webview -> server, relayed + * by the player's client). + * + * @param cb - gets the calling player's server id first (from FiveM + * `source`, never from the payload), then the event arguments. Its return + * value (awaited) is sent back to the caller + * + * @example + * rpc.onWebview('buyItem', (player, item) => shop.buy(player, item)) + */ public onWebview>( eventName: EventName, cb: RPCListener, @@ -122,12 +175,24 @@ export class RPCInstanceServer extends RPCInstanceBase { return this.listen(this._emitterWeb, 'onWebview', eventName, cb) } + /** Removes the `onWebview` listener for `eventName` */ public offWebview>( eventName: EventName, ): this { return this.unlisten(this._emitterWeb, 'offWebview', eventName) } + /** + * Calls the `onServer` listener in one player's webview (server -> webview, + * relayed by that player's client) and resolves with its return value. + * + * @param player - server id of the target player + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const confirmed = await rpc.emitWebview(player, 'confirmPurchase', item) + */ public async emitWebview< EventName extends RPCEventName, >( @@ -144,19 +209,39 @@ export class RPCInstanceServer extends RPCInstanceBase { // ===== SELF ===== + /** + * Listens for `emitSelf` calls in this environment (server -> server). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onSelf('add', (a, b) => a + b) + */ public onSelf>( eventName: EventName, - cb: RPCListener, + cb: RPCListener, ): this { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } + /** Removes the `onSelf` listener for `eventName` */ public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } + /** + * Calls this environment's own `onSelf` listener directly and resolves with + * its return value. No timeout; errors thrown by the listener reach the caller + * unchanged. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists + * + * @example + * const total = await rpc.emitSelf('add', 2, 3) + */ public async emitSelf>( eventName: EventName, ...args: RPCEventArgs @@ -166,6 +251,18 @@ export class RPCInstanceServer extends RPCInstanceBase { // ===== OTHER ===== + /** + * Registers a chat command (FiveM `RegisterCommand`). + * + * @param cb - gets the player's server id (`0` for the server console), the + * strings typed after the command and the full command line. Validate + * `args` yourself + * @param restricted - only players with the ACE permission `command.` + * can use it + * + * @example + * rpc.onCommand('heal', (player, args) => heal(player, Number(args[0])), true) + */ public onCommand>( command: CommandName, cb: (player: number, args: string[], rawCommand: string) => void, @@ -178,6 +275,15 @@ export class RPCInstanceServer extends RPCInstanceBase { return this } + /** + * Listens to a native FiveM server event, e.g. `playerJoining`. + * + * @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in + * `NATIVE_SERVER_EVENTS`. Register other events with FiveM's `on` directly + * + * @example + * rpc.onNativeEvent('playerJoining', (source, oldId) => console.log(source)) + */ public onNativeEvent( eventName: EventName, cb: (...args: Parameters) => void, diff --git a/rpc/src/core/webview.ts b/rpc/src/core/webview.ts index 17836e1..39d4608 100644 --- a/rpc/src/core/webview.ts +++ b/rpc/src/core/webview.ts @@ -16,6 +16,18 @@ import type { } from '../utils/typing' import { RPCInstanceBase } from './base' +/** + * RPC instance for webview code, returned by `createRPC({ env: 'webview' })`. + * Create one per webview and import it from your own module. + * + * - `on*` registers the listener that answers calls from one direction. One + * listener per event: registering the same name again replaces it, `off*` + * removes it + * - `emit*` calls the listener on the target and resolves with its return + * value, or rejects with {@link RPCError} + * - calls to and from the server are relayed by the player's client, which + * must run `createRPC({ env: 'client' })` + */ export class RPCInstanceWebview extends RPCInstanceBase { private readonly _emitterClient: Emitter private readonly _emitterServer: Emitter @@ -69,6 +81,16 @@ export class RPCInstanceWebview extends RPCInstanceBase { // ===== CLIENT ===== + /** + * Listens for `emitWebview` calls from this player's client + * (client -> webview). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onClient('openMenu', items => menu.open(items)) + */ public onClient>( eventName: EventName, cb: RPCListener, @@ -76,12 +98,23 @@ export class RPCInstanceWebview extends RPCInstanceBase { return this.listen(this._emitterClient, 'onClient', eventName, cb) } + /** Removes the `onClient` listener for `eventName` */ public offClient>( eventName: EventName, ): this { return this.unlisten(this._emitterClient, 'offClient', eventName) } + /** + * Calls the client's `onWebview` listener (webview -> client) and resolves + * with its return value. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const position = await rpc.emitClient('getPosition') + */ public async emitClient< EventName extends RPCEventName, >( @@ -95,6 +128,16 @@ export class RPCInstanceWebview extends RPCInstanceBase { // ===== SERVER ===== + /** + * Listens for `emitWebview` calls from the server (server -> webview, + * relayed by the client). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onServer('confirmPurchase', item => window.confirm(`Buy ${item}?`)) + */ public onServer>( eventName: EventName, cb: RPCListener, @@ -102,12 +145,24 @@ export class RPCInstanceWebview extends RPCInstanceBase { return this.listen(this._emitterServer, 'onServer', eventName, cb) } + /** Removes the `onServer` listener for `eventName` */ public offServer>( eventName: EventName, ): this { return this.unlisten(this._emitterServer, 'offServer', eventName) } + /** + * Calls the server's `onWebview` listener (webview -> server, relayed by + * the client) and resolves with its return value. The server listener gets + * this player's id first. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener), + * `HANDLER_ERROR` (the listener threw) or `TIMEOUT` + * + * @example + * const bought = await rpc.emitServer('buyItem', 'water') + */ public async emitServer< EventName extends RPCEventName, >( @@ -121,6 +176,15 @@ export class RPCInstanceWebview extends RPCInstanceBase { // ===== SELF ===== + /** + * Listens for `emitSelf` calls in this environment (webview -> webview). + * + * @param cb - gets the event arguments. Its return value (awaited) is sent + * back to the caller + * + * @example + * rpc.onSelf('add', (a, b) => a + b) + */ public onSelf>( eventName: EventName, cb: RPCListener, @@ -128,12 +192,23 @@ export class RPCInstanceWebview extends RPCInstanceBase { return this.listen(this._emitterLocal, 'onSelf', eventName, cb) } + /** Removes the `onSelf` listener for `eventName` */ public offSelf>( eventName: EventName, ): this { return this.unlisten(this._emitterLocal, 'offSelf', eventName) } + /** + * Calls this environment's own `onSelf` listener directly and resolves with + * its return value. No timeout; errors thrown by the listener reach the caller + * unchanged. + * + * @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists + * + * @example + * const total = await rpc.emitSelf('add', 2, 3) + */ public async emitSelf>( eventName: EventName, ...args: RPCEventArgs diff --git a/rpc/src/index.ts b/rpc/src/index.ts index 180f773..46612d3 100644 --- a/rpc/src/index.ts +++ b/rpc/src/index.ts @@ -12,7 +12,11 @@ import { /** * Creates the RPC instance for one environment. Create exactly one per * environment (server, client, webview) and export it from a local module. + * Every client needs one, even without listeners: it relays calls between + * its webview and the server. * + * @returns `RPCInstanceServer`, `RPCInstanceClient` or `RPCInstanceWebview`, + * matching `config.env` * @throws {@link RPCError} `UNKNOWN_ENVIRONMENT` if `config.env` is not * `'server'`, `'client'` or `'webview'` * diff --git a/rpc/src/utils/native.ts b/rpc/src/utils/native.ts index 349c354..61dd821 100644 --- a/rpc/src/utils/native.ts +++ b/rpc/src/utils/native.ts @@ -23,7 +23,10 @@ const serverEvents = { weaponDamageEvent: true, } satisfies Record -/** https://docs.fivem.net/docs/scripting-reference/events/server-events/ */ +/** + * Events `onNativeEvent` accepts on the server: + * https://docs.fivem.net/docs/scripting-reference/events/server-events/ + */ export const NATIVE_SERVER_EVENTS = Object.keys( serverEvents, ) as readonly (keyof RPCNativeServerEvents)[] @@ -41,12 +44,16 @@ const clientEvents = { populationPedCreating: true, } satisfies Record -/** https://docs.fivem.net/docs/scripting-reference/events/client-events/ */ +/** + * Events `onNativeEvent` accepts on the client: + * https://docs.fivem.net/docs/scripting-reference/events/client-events/ + */ export const NATIVE_CLIENT_EVENTS = Object.keys( clientEvents, ) as readonly (keyof RPCNativeClientEvents)[] /** + * Events `onNativeNetworkEvent` accepts on the client: * https://docs.fivem.net/docs/game-references/game-events/ * * Source of truth for `RPCNativeClientNetworkEventsNames`. diff --git a/rpc/src/utils/types.ts b/rpc/src/utils/types.ts index b42cecb..b7b5d1d 100644 --- a/rpc/src/utils/types.ts +++ b/rpc/src/utils/types.ts @@ -4,7 +4,8 @@ import type { RPCInstanceWebview } from '../core/webview' import type { NATIVE_CLIENT_NETWORK_EVENTS } from './native' /** - * Possible environment states for `RPCConfig` + * Where an instance runs: `server` (server scripts), `client` (client + * scripts) or `webview` (NUI page) */ export type RPCEnvironment = 'server' | 'client' | 'webview' @@ -99,18 +100,22 @@ export enum RPCEvents { LISTENER_WEB = '__rpc:listenerWeb', } -/** - * Errors to check against - */ +/** Values of `RPCError.code` */ export enum RPCErrors { + /** The target has no listener for the event (or `emitSelf` has no `onSelf`) */ EVENT_NOT_REGISTERED = 'Event not registered', + /** `onNative*` got an event that is not in its `NATIVE_*` list */ UNKNOWN_NATIVE = 'Unknown native event', + /** `createRPC` got an `env` other than server, client or webview */ UNKNOWN_ENVIRONMENT = 'Unknown environment', + /** No response within `RPCConfig.timeout` */ TIMEOUT = 'Timed out waiting for response', + /** The listener on the target threw; the message carries its error */ HANDLER_ERROR = 'Listener threw an error', } /** + * Native server events accepted by `onNativeEvent` on the server: * https://docs.fivem.net/docs/scripting-reference/events/server-events/ */ export type RPCNativeServerEvents = { @@ -245,6 +250,7 @@ export type RPCNativeServerEvents = { } /** + * Native client events accepted by `onNativeEvent` on the client: * https://docs.fivem.net/docs/scripting-reference/events/client-events/ */ export type RPCNativeClientEvents = { @@ -277,6 +283,7 @@ export type RPCNativeClientEvents = { ): void } +/** Game events accepted by `onNativeNetworkEvent` on the client */ export type RPCNativeClientNetworkEvents = { [name in RPCNativeClientNetworkEventsNames]: ( entities: number[], @@ -285,6 +292,9 @@ export type RPCNativeClientNetworkEvents = { ) => void } -/** https://docs.fivem.net/docs/game-references/game-events/ */ +/** + * Names in `NATIVE_CLIENT_NETWORK_EVENTS`: + * https://docs.fivem.net/docs/game-references/game-events/ + */ export type RPCNativeClientNetworkEventsNames = (typeof NATIVE_CLIENT_NETWORK_EVENTS)[number] diff --git a/shared-types/readme.md b/shared-types/readme.md index 6061261..8913a69 100644 --- a/shared-types/readme.md +++ b/shared-types/readme.md @@ -67,7 +67,7 @@ Each interface is one direction. An interface you leave empty stays loose (any n | `RPCCommands_Client` | - | - | `onCommand` (client) | | `RPCCommands_Server` | - | - | `onCommand` (server) | -- events: the member name is the event name, its parameters are the arguments, its return type is what `emit*` resolves with. Server listeners get the player id before the declared arguments. Names that are not identifiers work too: `'buy-item'(item: string): boolean` +- events: the member name is the event name, its parameters are the arguments, its return type is what `emit*` resolves with. Server `onClient` and `onWebview` listeners get the player id before the declared arguments. Names that are not identifiers work too: `'buy-item'(item: string): boolean` - commands: the key is the command name, the value is not used (`true`) ## License From 9a9667c48017b6c307d5c25e5468e5fac5c05d81 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:49:52 +0100 Subject: [PATCH 30/33] docs: move direction matrix to root readme.md --- rpc/readme.md | 19 +++++++++++++++++++ shared-types/readme.md | 16 +--------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/rpc/readme.md b/rpc/readme.md index ebd86f9..8176e12 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -57,6 +57,25 @@ try { ## How it works +### Directions + +Every call goes from an `emit*` method in one environment to the matching `on*` listener in another. The last column is the [shared-types](../shared-types/readme.md) interface that types it + +| From | Call | To | Listener | Typed by | +| ------- | ------------------------------------- | ----------- | ------------------------------------- | ------------------------- | +| server | `emitClient(player, event, ...args)` | client | `onServer` | `RPCEvents_ServerClient` | +| server | `emitClientEveryone(event, ...args)` | all clients | `onServer`, no response | `RPCEvents_ServerClient` | +| server | `emitWebview(player, event, ...args)` | webview | `onServer`, via client | `RPCEvents_ServerWebview` | +| server | `emitSelf(event, ...args)` | server | `onSelf` | `RPCEvents_Server` | +| client | `emitServer(event, ...args)` | server | `onClient`, player first | `RPCEvents_ClientServer` | +| client | `emitWebview(event, ...args)` | webview | `onClient` | `RPCEvents_ClientWebview` | +| client | `emitSelf(event, ...args)` | client | `onSelf` | `RPCEvents_Client` | +| webview | `emitServer(event, ...args)` | server | `onWebview`, player first, via client | `RPCEvents_WebviewServer` | +| webview | `emitClient(event, ...args)` | client | `onWebview` | `RPCEvents_WebviewClient` | +| webview | `emitSelf(event, ...args)` | webview | `onSelf` | `RPCEvents_Webview` | + +Commands registered with `onCommand` are typed by `RPCCommands_Server` and `RPCCommands_Client` + ### Routing Server and client talk over FiveM network events, client and webview over NUI messages and NUI callbacks. Webview and server never talk directly: every call between them is relayed by the client of that player. So every client must run `createRPC({ env: 'client' })`, even with no listeners of its own, or those calls time out diff --git a/shared-types/readme.md b/shared-types/readme.md index 8913a69..c0abcd9 100644 --- a/shared-types/readme.md +++ b/shared-types/readme.md @@ -51,21 +51,7 @@ See the [main readme](../readme.md#installation). The declaration file below imp ## Interfaces -Each interface is one direction. An interface you leave empty stays loose (any name, arguments and result), so you can declare them one at a time - -| Interface | Direction | Caller | Listener | -| ------------------------- | ------------------ | ------------------------------------------- | -------------------- | -| `RPCEvents_Client` | client -> client | `emitSelf` (client) | `onSelf` (client) | -| `RPCEvents_ClientServer` | client -> server | `emitServer` (client) | `onClient` (server) | -| `RPCEvents_ClientWebview` | client -> webview | `emitWebview` (client) | `onClient` (webview) | -| `RPCEvents_Server` | server -> server | `emitSelf` (server) | `onSelf` (server) | -| `RPCEvents_ServerClient` | server -> client | `emitClient`, `emitClientEveryone` (server) | `onServer` (client) | -| `RPCEvents_ServerWebview` | server -> webview | `emitWebview` (server) | `onServer` (webview) | -| `RPCEvents_Webview` | webview -> webview | `emitSelf` (webview) | `onSelf` (webview) | -| `RPCEvents_WebviewClient` | webview -> client | `emitClient` (webview) | `onWebview` (client) | -| `RPCEvents_WebviewServer` | webview -> server | `emitServer` (webview) | `onWebview` (server) | -| `RPCCommands_Client` | - | - | `onCommand` (client) | -| `RPCCommands_Server` | - | - | `onCommand` (server) | +Each interface types one direction, see the [direction table](../rpc/readme.md#directions) for which `emit*` and `on*` methods use it. `RPCCommands_Server` and `RPCCommands_Client` type `onCommand`. An interface you leave empty stays loose (any name, arguments and result), so you can declare them one at a time - events: the member name is the event name, its parameters are the arguments, its return type is what `emit*` resolves with. Server `onClient` and `onWebview` listeners get the player id before the declared arguments. Names that are not identifiers work too: `'buy-item'(item: string): boolean` - commands: the key is the command name, the value is not used (`true`) From 28b1f96b507f34b0fab9df6d71dbf456cebae669 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 01:56:09 +0100 Subject: [PATCH 31/33] feat(rpc): introduce agent skill for extended usage --- readme.md | 2 + rpc/package.json | 1 + rpc/readme.md | 10 ++++ rpc/skills/fivem-rpc/SKILL.md | 105 ++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 rpc/skills/fivem-rpc/SKILL.md diff --git a/readme.md b/readme.md index 5abcb51..1996a72 100644 --- a/readme.md +++ b/readme.md @@ -72,6 +72,8 @@ All methods: [API reference](rpc/readme.md). Issues and pull requests are very welcome +When the API changes, update the TSDoc, the [direction table](rpc/readme.md#directions) and the [agent skill](rpc/skills/fivem-rpc/SKILL.md) + Releases are published with `bun publish`, which replaces the `workspace:^` dependency on shared-types with its version (`npm publish` would not) ## License diff --git a/rpc/package.json b/rpc/package.json index 3b1a0c3..3a67501 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -32,6 +32,7 @@ }, "files": [ "dist/**/*", + "skills/**/*", "readme.md", "license.md" ], diff --git a/rpc/readme.md b/rpc/readme.md index 8176e12..8e4ee18 100644 --- a/rpc/readme.md +++ b/rpc/readme.md @@ -421,6 +421,16 @@ const response = await rpc.emitSelf('webviewEvent', someData) // response will come from webview listener with returned data ``` +## Using with AI agents + +The package ships an [Agent Skill](https://agentskills.io) with the directions, rules, typing and error fixes above. Copy it into your agent's skills folder, e.g. for Claude Code: + +```bash +cp -r node_modules/@entityseven/fivem-rpc/skills/fivem-rpc .claude/skills/ +``` + +Copy it again after upgrading. Every method also carries TSDoc with its direction and matching listener + ## License Licensed under the [Custom Attribution-NoDerivs Software License](license.md) diff --git a/rpc/skills/fivem-rpc/SKILL.md b/rpc/skills/fivem-rpc/SKILL.md new file mode 100644 index 0000000..77f0dc8 --- /dev/null +++ b/rpc/skills/fivem-rpc/SKILL.md @@ -0,0 +1,105 @@ +--- +name: fivem-rpc +description: Use when writing FiveM server, client or NUI (webview) code that calls between environments with @entityseven/fivem-rpc (createRPC, emitServer, onClient, emitWebview, onServer, ...), or when declaring its typed events in @entityseven/fivem-rpc-shared-types. +--- + +# @entityseven/fivem-rpc + +Typed async calls between FiveM server, client and webview (NUI). Every `emit*` resolves with the return value of the matching `on*` listener in another environment + +## Setup + +One instance per environment, created once in a local module and imported everywhere else: + +```ts +// server/rpc.ts, same in client/rpc.ts with env 'client' and webview/rpc.ts with env 'webview' +import { createRPC } from '@entityseven/fivem-rpc' +export const rpc = createRPC({ env: 'server' }) // options: debug (false), timeout (5000 ms, 0 = none) +``` + +## Directions + +Method names are relative to the environment: `onClient` on the server listens to clients, `onClient` in the webview listens to its client + +| From | Call | To | Listener | Typed by | +| ------- | ------------------------------------- | ----------- | ------------------------------------- | ------------------------- | +| server | `emitClient(player, event, ...args)` | client | `onServer` | `RPCEvents_ServerClient` | +| server | `emitClientEveryone(event, ...args)` | all clients | `onServer`, no response | `RPCEvents_ServerClient` | +| server | `emitWebview(player, event, ...args)` | webview | `onServer`, via client | `RPCEvents_ServerWebview` | +| server | `emitSelf(event, ...args)` | server | `onSelf` | `RPCEvents_Server` | +| client | `emitServer(event, ...args)` | server | `onClient`, player first | `RPCEvents_ClientServer` | +| client | `emitWebview(event, ...args)` | webview | `onClient` | `RPCEvents_ClientWebview` | +| client | `emitSelf(event, ...args)` | client | `onSelf` | `RPCEvents_Client` | +| webview | `emitServer(event, ...args)` | server | `onWebview`, player first, via client | `RPCEvents_WebviewServer` | +| webview | `emitClient(event, ...args)` | client | `onWebview` | `RPCEvents_WebviewClient` | +| webview | `emitSelf(event, ...args)` | webview | `onSelf` | `RPCEvents_Webview` | + +Commands registered with `onCommand` are typed by `RPCCommands_Server` and `RPCCommands_Client` + +## Rules + +- every `emit*` needs its listener registered in the target environment (see Directions), otherwise it rejects with `EVENT_NOT_REGISTERED` +- webview <-> server always goes through the player's client: the client must call `createRPC({ env: 'client' })` even with no listeners, otherwise those calls time out +- one listener per event and direction: registering the same name again replaces it, `off*` removes it +- always `await` or `.catch()` an `emit*`: it rejects with `RPCError`. `emitClientEveryone` is one-way and resolves once sent +- server `onClient` and `onWebview` listeners get the caller's server id first, taken from FiveM `source`. Use it, never trust player ids passed as arguments +- arguments and return values travel as JSON: pass plain data (no functions, class instances, `Map`, `Set`) +- never register FiveM events or NUI callbacks named `__rpc:*`, the library owns them +- `onNativeEvent` and `onNativeNetworkEvent` only accept names from `NATIVE_SERVER_EVENTS`, `NATIVE_CLIENT_EVENTS` and `NATIVE_CLIENT_NETWORK_EVENTS`. Use FiveM `on(name, cb)` for anything else + +## Typing + +Declare events in one `.d.ts` file included by the `tsconfig.json` of every environment. Without declarations every name, argument and result is `any` + +```ts +// shared/rpc.d.ts +import '@entityseven/fivem-rpc-shared-types' // required: without it the declaration replaces the module + +declare module '@entityseven/fivem-rpc-shared-types' { + interface RPCEvents_WebviewServer { + // event name(arguments): value the listener returns + buyItem(item: string): boolean + } + interface RPCEvents_ServerClient { + itemBought(item: string): void + } + interface RPCCommands_Server { + ban: true // commands are keys, the value is not used + } +} +``` + +Leave an interface empty to keep that direction untyped. Declare plain return values, listeners may still be `async` + +## Example + +```ts +// server +rpc.onWebview('buyItem', async (player, item) => { + const ok = await chargePlayer(player, item) + if (ok) await rpc.emitClient(player, 'itemBought', item) + return ok +}) + +// client +rpc.onServer('itemBought', item => { + // update HUD, play a sound +}) + +// webview +const ok = await rpc.emitServer('buyItem', 'water') +``` + +## Errors + +`RPCError.code` is one of `RPCErrors`, the message names the fix + +| Code | Cause | Fix | +| ---------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| `EVENT_NOT_REGISTERED` | no listener for the event on the target | register the listener from the Directions table on the target | +| `TIMEOUT` | no response within `timeout` (default 5000 ms) | listener must return or resolve; client needs `createRPC` for webview <-> server; raise `timeout` | +| `HANDLER_ERROR` | the listener threw, its message is included | fix the listener, the stack is logged with `console.error` on the target | +| `UNKNOWN_NATIVE` | `onNative*` name not in its `NATIVE_*` list | use FiveM `on(name, cb)` | +| `UNKNOWN_ENVIRONMENT` | `createRPC` got an `env` other than server, client, webview | fix `env` | + +Debug with `createRPC({ env, debug: true })`: logs every registration, call and payload From 9f949d39503fee43b3e92ade180bb1ccb69a3735 Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 02:00:43 +0100 Subject: [PATCH 32/33] chore: bump versions --- rpc/package.json | 2 +- shared-types/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/package.json b/rpc/package.json index 3a67501..ebe996b 100644 --- a/rpc/package.json +++ b/rpc/package.json @@ -1,6 +1,6 @@ { "name": "@entityseven/fivem-rpc", - "version": "0.1.0", + "version": "1.0.0", "description": "Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong", "keywords": [ "cfx", diff --git a/shared-types/package.json b/shared-types/package.json index 04a85db..7ad6c3a 100644 --- a/shared-types/package.json +++ b/shared-types/package.json @@ -1,6 +1,6 @@ { "name": "@entityseven/fivem-rpc-shared-types", - "version": "0.1.0", + "version": "1.0.0", "description": "Event and command declarations for @entityseven/fivem-rpc, for typed events", "keywords": [ "cfx", From d1d4d91a5eed3d9733e39b30625038652683e46e Mon Sep 17 00:00:00 2001 From: Danya H Date: Mon, 28 Sep 2026 02:10:14 +0100 Subject: [PATCH 33/33] chore: bump dependency version --- bun.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 681717c..1f64568 100644 --- a/bun.lock +++ b/bun.lock @@ -11,7 +11,7 @@ }, "rpc": { "name": "@entityseven/fivem-rpc", - "version": "0.1.0", + "version": "1.0.0", "dependencies": { "@entityseven/fivem-rpc-shared-types": "workspace:^", }, @@ -27,7 +27,7 @@ }, "shared-types": { "name": "@entityseven/fivem-rpc-shared-types", - "version": "0.1.0", + "version": "1.0.0", "peerDependencies": { "typescript": ">=5", },