@laot/nuix 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -41
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,56 +4,101 @@
|
|
|
4
4
|
[](https://github.com/laot7490/nuix/blob/main/LICENSE)
|
|
5
5
|
[](https://bundlephobia.com/package/@laot/nuix)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A type-safe TypeScript helper library for FiveM NUI development. Wraps the most common NUI patterns — fetching data from Lua, listening for messages, formatting strings, and handling translations — in a clean, fully typed API. Zero runtime dependencies, works with any frontend framework.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Install](#install)
|
|
14
|
+
- [Quick Start](#quick-start)
|
|
15
|
+
- [Event Maps](#1-event-maps)
|
|
16
|
+
- [fetchNui](#2-fetchnui--typed-lua-callbacks)
|
|
17
|
+
- [onNuiMessage](#3-onnuimessage--listening-to-lua)
|
|
18
|
+
- [luaFormat](#4-luaformat--string-formatting)
|
|
19
|
+
- [Translator (Global)](#5-translator-global)
|
|
20
|
+
- [Translator (Isolated)](#6-translator-isolated)
|
|
21
|
+
- [Debug Mode](#7-debug-mode)
|
|
22
|
+
- [Mock Data](#8-mock-data-local-development)
|
|
23
|
+
- [Lua Side](#lua-side)
|
|
24
|
+
- [API Reference](#api-reference)
|
|
25
|
+
- [Build](#build)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
---
|
|
8
29
|
|
|
9
30
|
## Install
|
|
10
31
|
|
|
11
32
|
```bash
|
|
33
|
+
# pick your package manager
|
|
12
34
|
npm install @laot/nuix
|
|
13
35
|
pnpm add @laot/nuix
|
|
14
36
|
yarn add @laot/nuix
|
|
15
37
|
bun add @laot/nuix
|
|
16
38
|
```
|
|
17
39
|
|
|
40
|
+
---
|
|
41
|
+
|
|
18
42
|
## Quick Start
|
|
19
43
|
|
|
20
|
-
### 1.
|
|
44
|
+
### 1. Event Maps
|
|
45
|
+
|
|
46
|
+
Before using anything, you'll want to define your events. NUIX uses these maps to infer the exact types for both data you send and responses you get back. You'll typically have two separate maps:
|
|
47
|
+
|
|
48
|
+
- **Callback events** — for `fetchNui` calls. Your TS code sends data to Lua, Lua processes it and sends a response back.
|
|
49
|
+
- **Message events** — for `onNuiMessage` listeners. Lua pushes data to TS via `SendNUIMessage`, no response needed.
|
|
21
50
|
|
|
22
51
|
```ts
|
|
23
52
|
import type { NuiEventMap } from "@laot/nuix";
|
|
24
53
|
|
|
25
|
-
//
|
|
54
|
+
// Things you ASK Lua for (request → response)
|
|
26
55
|
interface CallbackEvents extends NuiEventMap {
|
|
27
56
|
getPlayer: { data: { id: number }; response: { name: string; level: number } };
|
|
28
57
|
sendNotify: { data: { message: string }; response: void };
|
|
29
58
|
}
|
|
30
59
|
|
|
31
|
-
// Lua
|
|
60
|
+
// Things Lua TELLS you about (one-way push)
|
|
32
61
|
interface MessageEvents extends NuiEventMap {
|
|
33
62
|
showMenu: { data: { items: string[] }; response: void };
|
|
34
63
|
hideMenu: { data: void; response: void };
|
|
35
64
|
}
|
|
36
65
|
```
|
|
37
66
|
|
|
38
|
-
|
|
67
|
+
> Keeping them separate isn't mandatory, but it makes your code way easier to reason about — you'll always know which events go where.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### 2. `fetchNui` — Typed Lua Callbacks
|
|
72
|
+
|
|
73
|
+
`createFetchNui` gives you a typed function that POSTs JSON to `https://<resourceName>/<event>`, matching `RegisterNUICallback` on the Lua side. The resource name is automatically grabbed from FiveM's `GetParentResourceName()`.
|
|
39
74
|
|
|
40
75
|
```ts
|
|
41
76
|
import { createFetchNui } from "@laot/nuix";
|
|
42
77
|
|
|
43
78
|
const fetchNui = createFetchNui<CallbackEvents>();
|
|
44
79
|
|
|
80
|
+
// fully typed — player is { name: string; level: number }
|
|
45
81
|
const player = await fetchNui("getPlayer", { id: 1 });
|
|
46
|
-
console.log(player.name);
|
|
82
|
+
console.log(player.name, player.level);
|
|
47
83
|
|
|
84
|
+
// void response — you're just notifying Lua, no return value
|
|
48
85
|
await fetchNui("sendNotify", { message: "Hello!" });
|
|
86
|
+
```
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
You can also set a **timeout** to avoid hanging forever if the Lua callback never responds:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
51
91
|
const data = await fetchNui("getPlayer", { id: 2 }, { timeout: 5000 });
|
|
92
|
+
// rejects with "[NUIX] fetchNui("getPlayer") timed out after 5000ms" if no response
|
|
52
93
|
```
|
|
53
94
|
|
|
54
|
-
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 3. `onNuiMessage` — Listening to Lua
|
|
55
98
|
|
|
56
|
-
|
|
99
|
+
Listens for messages from Lua's `SendNUIMessage`. There are two ways to use it:
|
|
100
|
+
|
|
101
|
+
**Switch-case** — one listener that handles every action:
|
|
57
102
|
|
|
58
103
|
```ts
|
|
59
104
|
import { onNuiMessage } from "@laot/nuix";
|
|
@@ -69,10 +114,11 @@ const unsub = onNuiMessage<MessageEvents>((action, data) => {
|
|
|
69
114
|
}
|
|
70
115
|
});
|
|
71
116
|
|
|
72
|
-
|
|
117
|
+
// when you're done listening
|
|
118
|
+
unsub();
|
|
73
119
|
```
|
|
74
120
|
|
|
75
|
-
**Per-action** —
|
|
121
|
+
**Per-action** — filters by action name, and `data` is fully typed automatically:
|
|
76
122
|
|
|
77
123
|
```ts
|
|
78
124
|
const unsub = onNuiMessage<MessageEvents, "showMenu">("showMenu", (data) => {
|
|
@@ -80,7 +126,20 @@ const unsub = onNuiMessage<MessageEvents, "showMenu">("showMenu", (data) => {
|
|
|
80
126
|
});
|
|
81
127
|
```
|
|
82
128
|
|
|
83
|
-
|
|
129
|
+
Both overloads return an `UnsubscribeFn` — just call it to remove the event listener.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### 4. `luaFormat` — String Formatting
|
|
134
|
+
|
|
135
|
+
A small utility that formats strings using Lua-style placeholders. Handles `null` and `undefined` safely instead of crashing.
|
|
136
|
+
|
|
137
|
+
| Specifier | What it does |
|
|
138
|
+
|-----------|--------------------------------------|
|
|
139
|
+
| `%s` | String (null/undefined → `""`) |
|
|
140
|
+
| `%d` / `%i` | Integer (floors the value, NaN → `0`) |
|
|
141
|
+
| `%f` | Float (NaN → `0`) |
|
|
142
|
+
| `%%` | Literal `%` sign |
|
|
84
143
|
|
|
85
144
|
```ts
|
|
86
145
|
import { luaFormat } from "@laot/nuix";
|
|
@@ -90,11 +149,18 @@ luaFormat("Hello %s, you are level %d", "Laot", 42);
|
|
|
90
149
|
|
|
91
150
|
luaFormat("Accuracy: %f%%", 99.5);
|
|
92
151
|
// → "Accuracy: 99.5%"
|
|
152
|
+
|
|
153
|
+
luaFormat("Safe: %s %d", undefined, NaN);
|
|
154
|
+
// → "Safe: 0"
|
|
93
155
|
```
|
|
94
156
|
|
|
157
|
+
---
|
|
158
|
+
|
|
95
159
|
### 5. Translator (Global)
|
|
96
160
|
|
|
97
|
-
|
|
161
|
+
A global translation system built on top of `luaFormat`. The idea is simple: Lua sends locale data once (usually on resource start), you register it, and then use `_U()` anywhere in your UI to get translated strings.
|
|
162
|
+
|
|
163
|
+
**Registration:**
|
|
98
164
|
|
|
99
165
|
```ts
|
|
100
166
|
import { registerLocales, _U, onNuiMessage } from "@laot/nuix";
|
|
@@ -108,35 +174,44 @@ interface Events extends NuiEventMap {
|
|
|
108
174
|
onNuiMessage<Events>((action, data) => {
|
|
109
175
|
switch (action) {
|
|
110
176
|
case "setLocales":
|
|
111
|
-
registerLocales(data);
|
|
177
|
+
registerLocales(data); // store the locale map globally
|
|
112
178
|
break;
|
|
113
179
|
case "showMenu":
|
|
114
180
|
openMenu(data.items);
|
|
115
181
|
break;
|
|
116
182
|
}
|
|
117
183
|
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Usage — anywhere in your app:**
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// assuming Lua sent: { ui: { greeting: "Hello %s!", level: "Level %d" } }
|
|
118
190
|
|
|
119
|
-
// Use _U anywhere
|
|
120
191
|
_U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
121
192
|
_U("ui.level", "Lv.", 42); // → "Level 42"
|
|
122
|
-
_U("missing.key", "Fallback"); // → "Fallback"
|
|
193
|
+
_U("missing.key", "Fallback"); // → "Fallback" (key not found, returns fallback)
|
|
123
194
|
```
|
|
124
195
|
|
|
125
|
-
|
|
196
|
+
**Adding more translations later** without overwriting existing ones:
|
|
126
197
|
|
|
127
198
|
```ts
|
|
128
199
|
import { extendLocales } from "@laot/nuix";
|
|
129
200
|
|
|
130
201
|
extendLocales({ ui: { subtitle: "Overview" } });
|
|
131
|
-
//
|
|
202
|
+
// merges into the existing locale map — won't touch other keys
|
|
132
203
|
```
|
|
133
204
|
|
|
205
|
+
> `_U` uses dot notation. `"ui.greeting"` looks up `locales.ui.greeting` under the hood.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
134
209
|
### 6. Translator (Isolated)
|
|
135
210
|
|
|
136
|
-
If you need a
|
|
211
|
+
If you need a translator that's completely independent from the global `_U` — maybe a component with its own locale scope — use `createTranslator`:
|
|
137
212
|
|
|
138
213
|
```ts
|
|
139
|
-
import { createTranslator
|
|
214
|
+
import { createTranslator } from "@laot/nuix";
|
|
140
215
|
|
|
141
216
|
const _T = createTranslator({
|
|
142
217
|
locales: {
|
|
@@ -147,34 +222,50 @@ const _T = createTranslator({
|
|
|
147
222
|
|
|
148
223
|
_T("greeting", "MISSING", "Laot"); // → "Hello Laot!"
|
|
149
224
|
_T("level", "MISSING", 42); // → "Level 42"
|
|
225
|
+
_T("no.key", "Not found"); // → "Not found"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
There's also `mergeLocales` if you need to deep-merge locale records manually:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { mergeLocales } from "@laot/nuix";
|
|
150
232
|
|
|
151
|
-
// Deep-merge multiple locale records
|
|
152
233
|
const base = { ui: { greeting: "Hello %s!" } };
|
|
153
234
|
const patch = { ui: { greeting: "Hey %s, welcome back!" } };
|
|
235
|
+
|
|
154
236
|
const merged = mergeLocales(base, patch);
|
|
237
|
+
// merged.ui.greeting → "Hey %s, welcome back!"
|
|
155
238
|
```
|
|
156
239
|
|
|
240
|
+
---
|
|
241
|
+
|
|
157
242
|
### 7. Debug Mode
|
|
158
243
|
|
|
159
|
-
|
|
244
|
+
Pass `debug: true` to `createFetchNui` and every call will be logged to the console with the `[NUIX]` prefix. Super useful during development:
|
|
160
245
|
|
|
161
246
|
```ts
|
|
162
247
|
const fetchNui = createFetchNui<CallbackEvents>({ debug: true });
|
|
163
248
|
|
|
164
249
|
await fetchNui("getPlayer", { id: 1 });
|
|
250
|
+
// Console:
|
|
165
251
|
// [NUIX] → getPlayer { id: 1 }
|
|
166
252
|
// [NUIX] ← getPlayer { name: "Laot", level: 42 }
|
|
167
253
|
```
|
|
168
254
|
|
|
255
|
+
---
|
|
256
|
+
|
|
169
257
|
### 8. Mock Data (Local Development)
|
|
170
258
|
|
|
171
|
-
|
|
259
|
+
When you're building your UI outside of FiveM (like in a regular browser with `npm run dev`), there's no Lua backend to respond to your `fetchNui` calls. That's where `mockData` comes in — it returns pre-defined responses without making any HTTP requests.
|
|
172
260
|
|
|
173
261
|
```ts
|
|
174
262
|
const fetchNui = createFetchNui<CallbackEvents>({
|
|
175
263
|
debug: true,
|
|
176
264
|
mockData: {
|
|
265
|
+
// static response — just return this object every time
|
|
177
266
|
getPlayer: { name: "DevPlayer", level: 99 },
|
|
267
|
+
|
|
268
|
+
// dynamic response — receive the data, return something based on it
|
|
178
269
|
sendNotify: (data) => {
|
|
179
270
|
console.log("Mock notification:", data.message);
|
|
180
271
|
},
|
|
@@ -182,47 +273,79 @@ const fetchNui = createFetchNui<CallbackEvents>({
|
|
|
182
273
|
});
|
|
183
274
|
|
|
184
275
|
const player = await fetchNui("getPlayer", { id: 1 });
|
|
276
|
+
// Console:
|
|
185
277
|
// [NUIX] → getPlayer { id: 1 }
|
|
186
278
|
// [NUIX] ← getPlayer (mock) { name: "DevPlayer", level: 99 }
|
|
187
279
|
```
|
|
188
280
|
|
|
189
|
-
|
|
281
|
+
> Works great combined with `debug: true` — you can see exactly what's being sent and received in the console.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Lua Side
|
|
286
|
+
|
|
287
|
+
Here's how the Lua side connects to everything above:
|
|
190
288
|
|
|
191
289
|
```lua
|
|
192
|
-
--
|
|
290
|
+
-- Responds to fetchNui("getPlayer", { id = ... })
|
|
193
291
|
RegisterNUICallback("getPlayer", function(data, cb)
|
|
194
292
|
local player = GetPlayerData(data.id)
|
|
195
293
|
cb({ name = player.name, level = player.level })
|
|
196
294
|
end)
|
|
197
295
|
|
|
198
|
-
--
|
|
296
|
+
-- Pushes a message to onNuiMessage listeners
|
|
199
297
|
SendNUIMessage({ action = "showMenu", data = { items = {"Pistol", "Rifle"} } })
|
|
200
298
|
|
|
201
|
-
--
|
|
299
|
+
-- Sends locale data for registerLocales
|
|
202
300
|
SendNUIMessage({ action = "setLocales", data = Locales })
|
|
203
301
|
```
|
|
204
302
|
|
|
303
|
+
---
|
|
304
|
+
|
|
205
305
|
## API Reference
|
|
206
306
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
|
210
|
-
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
307
|
+
### Functions
|
|
308
|
+
|
|
309
|
+
| Export | Description |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `createFetchNui<TMap>(options?)` | Returns a typed `fetchNui` function. Supports `debug` and `mockData` options. |
|
|
312
|
+
| `onNuiMessage<TMap>(handler)` | Listens to all NUI messages — use with a switch-case. |
|
|
313
|
+
| `onNuiMessage<TMap, K>(action, handler)` | Listens to a single action — `data` is automatically typed. |
|
|
314
|
+
| `luaFormat(template, ...args)` | Lua-style string formatter with `%s` / `%d` / `%f` support. |
|
|
315
|
+
| `registerLocales(locales)` | Sets the global locale map (replaces the current one). |
|
|
316
|
+
| `extendLocales(...records)` | Merges new entries into the existing global locale map. |
|
|
317
|
+
| `_U(key, fallback, ...args)` | Global translator — reads from the registered locale map. |
|
|
318
|
+
| `createTranslator(options)` | Returns an isolated translator function with its own locale scope. |
|
|
319
|
+
| `mergeLocales(...records)` | Deep-merges multiple locale records into one. |
|
|
320
|
+
|
|
321
|
+
### Types
|
|
322
|
+
|
|
323
|
+
| Export | Description |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `NuiEventMap` | Base interface for defining event maps. |
|
|
326
|
+
| `NuiMessagePayload<TData>` | Shape of `SendNUIMessage` payloads (`{ action, data }`). |
|
|
327
|
+
| `FetchNuiOptions` | Per-call options for `fetchNui` (e.g. `timeout`). |
|
|
328
|
+
| `FetchNuiFactoryOptions<TMap>` | Config for `createFetchNui` (`debug`, `mockData`). |
|
|
329
|
+
| `LocaleRecord` | Flat or nested string map used for translations. |
|
|
330
|
+
| `TranslatorOptions` | Config for `createTranslator`. |
|
|
331
|
+
| `TranslatorFn` | Translator function signature (`(key, fallback, ...args) => string`). |
|
|
332
|
+
| `FormatArg` | Accepted argument types for `luaFormat` (`string \| number \| boolean \| null \| undefined`). |
|
|
333
|
+
| `UnsubscribeFn` | Cleanup function returned by `onNuiMessage`. |
|
|
334
|
+
| `NuiMessageHandler<TData>` | Callback type for NUI message listeners. |
|
|
335
|
+
|
|
336
|
+
---
|
|
218
337
|
|
|
219
338
|
## Build
|
|
220
339
|
|
|
221
340
|
```bash
|
|
222
|
-
npm run build # ESM + CJS + .d.ts
|
|
223
|
-
npm run typecheck
|
|
341
|
+
npm run build # outputs ESM + CJS + .d.ts to dist/
|
|
342
|
+
npm run typecheck # tsc --noEmit
|
|
224
343
|
```
|
|
225
344
|
|
|
345
|
+
Requires Node.js ≥ 18.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
226
349
|
## License
|
|
227
350
|
|
|
228
|
-
MIT
|
|
351
|
+
[MIT](LICENSE) © LAOT
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extend this to map your NUI
|
|
3
|
-
* Both `fetchNui` and `onNuiMessage` use this map for
|
|
2
|
+
* Extend this to map your NUI event names to their data/response shapes.
|
|
3
|
+
* Both `fetchNui` and `onNuiMessage` use this map for type inference.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* ```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extend this to map your NUI
|
|
3
|
-
* Both `fetchNui` and `onNuiMessage` use this map for
|
|
2
|
+
* Extend this to map your NUI event names to their data/response shapes.
|
|
3
|
+
* Both `fetchNui` and `onNuiMessage` use this map for type inference.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* ```ts
|