@laot/nuix 1.0.0 → 1.0.1
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 +91 -48
- package/dist/index.cjs +30 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -21
- package/dist/index.d.ts +77 -21
- package/dist/index.js +26 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,8 +16,9 @@ npm install @laot/nuix
|
|
|
16
16
|
import type { NuiEventMap } from "@laot/nuix";
|
|
17
17
|
|
|
18
18
|
interface MyEvents extends NuiEventMap {
|
|
19
|
-
getPlayer:
|
|
19
|
+
getPlayer: { request: { id: number }; response: { name: string; level: number } };
|
|
20
20
|
sendNotify: { request: { message: string }; response: void };
|
|
21
|
+
showMenu: { request: { items: string[] }; response: void };
|
|
21
22
|
}
|
|
22
23
|
```
|
|
23
24
|
|
|
@@ -28,9 +29,8 @@ import { createFetchNui } from "@laot/nuix";
|
|
|
28
29
|
|
|
29
30
|
const fetchNui = createFetchNui<MyEvents>();
|
|
30
31
|
|
|
31
|
-
// Fully typed — request and response inferred from event map
|
|
32
32
|
const player = await fetchNui("getPlayer", { id: 1 });
|
|
33
|
-
console.log(player.name); //
|
|
33
|
+
console.log(player.name); // string
|
|
34
34
|
|
|
35
35
|
await fetchNui("sendNotify", { message: "Hello!" });
|
|
36
36
|
|
|
@@ -40,20 +40,31 @@ const data = await fetchNui("getPlayer", { id: 2 }, { timeout: 5000 });
|
|
|
40
40
|
|
|
41
41
|
### 3. NUI Message Listener
|
|
42
42
|
|
|
43
|
+
**Switch-case** — single listener for all actions:
|
|
44
|
+
|
|
43
45
|
```ts
|
|
44
46
|
import { onNuiMessage } from "@laot/nuix";
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const unsub = onNuiMessage<MyEvents>((action, data) => {
|
|
49
|
+
switch (action) {
|
|
50
|
+
case "getPlayer":
|
|
51
|
+
console.log(data.name);
|
|
52
|
+
break;
|
|
53
|
+
case "sendNotify":
|
|
54
|
+
console.log(data.message);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
unsub(); // stop listening
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Per-action** — filtered by action, `data` is fully typed:
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
```ts
|
|
65
|
+
const unsub = onNuiMessage<MyEvents, "showMenu">("showMenu", (data) => {
|
|
52
66
|
console.log(data.items); // ✅ typed as string[]
|
|
53
67
|
});
|
|
54
|
-
|
|
55
|
-
// Clean up
|
|
56
|
-
unsub();
|
|
57
68
|
```
|
|
58
69
|
|
|
59
70
|
### 4. Lua-Style Formatter
|
|
@@ -68,63 +79,89 @@ luaFormat("Accuracy: %f%%", 99.5);
|
|
|
68
79
|
// → "Accuracy: 99.5%"
|
|
69
80
|
```
|
|
70
81
|
|
|
71
|
-
### 5. Translator
|
|
82
|
+
### 5. Translator (Global)
|
|
83
|
+
|
|
84
|
+
Register locales once at runtime (e.g. when Lua sends them), then use `_U` anywhere:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { registerLocales, _U, onNuiMessage } from "@laot/nuix";
|
|
88
|
+
import type { NuiEventMap, LocaleRecord } from "@laot/nuix";
|
|
89
|
+
|
|
90
|
+
interface Events extends NuiEventMap {
|
|
91
|
+
setLocales: { request: LocaleRecord; response: void };
|
|
92
|
+
showMenu: { request: { items: string[] }; response: void };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onNuiMessage<Events>((action, data) => {
|
|
96
|
+
switch (action) {
|
|
97
|
+
case "setLocales":
|
|
98
|
+
registerLocales(data);
|
|
99
|
+
break;
|
|
100
|
+
case "showMenu":
|
|
101
|
+
openMenu(data.items);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Use _U anywhere
|
|
107
|
+
_U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
108
|
+
_U("ui.level", "Lv.", 42); // → "Level 42"
|
|
109
|
+
_U("missing.key", "Fallback"); // → "Fallback"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
You can also extend locales incrementally with `extendLocales`:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { extendLocales } from "@laot/nuix";
|
|
116
|
+
|
|
117
|
+
extendLocales({ ui: { subtitle: "Overview" } });
|
|
118
|
+
// Merges into existing locales without replacing them
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 6. Translator (Isolated)
|
|
122
|
+
|
|
123
|
+
If you need a separate translator instance with its own locale scope:
|
|
72
124
|
|
|
73
125
|
```ts
|
|
74
126
|
import { createTranslator, mergeLocales } from "@laot/nuix";
|
|
75
127
|
|
|
76
|
-
const
|
|
128
|
+
const _T = createTranslator({
|
|
77
129
|
locales: {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
level: "Level %d",
|
|
81
|
-
},
|
|
82
|
-
server: {
|
|
83
|
-
error: "Error: %s",
|
|
84
|
-
},
|
|
85
|
-
flat_key: "Plain message: %s",
|
|
130
|
+
greeting: "Hello %s!",
|
|
131
|
+
level: "Level %d",
|
|
86
132
|
},
|
|
87
133
|
});
|
|
88
134
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
_U("flat_key", "MISSING", "test"); // → "Plain message: test"
|
|
92
|
-
_U("no.key", "Not found"); // → "Not found"
|
|
93
|
-
|
|
94
|
-
// Deep-merge multiple locale files
|
|
95
|
-
const base = { client: { greeting: "Hello %s!" } };
|
|
96
|
-
const overrides = { client: { greeting: "Hey %s, welcome back!" } };
|
|
97
|
-
const merged = mergeLocales(base, overrides);
|
|
98
|
-
const _T = createTranslator({ locales: merged });
|
|
135
|
+
_T("greeting", "MISSING", "Laot"); // → "Hello Laot!"
|
|
136
|
+
_T("level", "MISSING", 42); // → "Level 42"
|
|
99
137
|
|
|
100
|
-
|
|
138
|
+
// Deep-merge multiple locale records
|
|
139
|
+
const base = { ui: { greeting: "Hello %s!" } };
|
|
140
|
+
const patch = { ui: { greeting: "Hey %s, welcome back!" } };
|
|
141
|
+
const merged = mergeLocales(base, patch);
|
|
101
142
|
```
|
|
102
143
|
|
|
103
|
-
###
|
|
144
|
+
### 7. Debug Mode
|
|
104
145
|
|
|
105
|
-
Enable console logging for every `fetchNui` call
|
|
146
|
+
Enable console logging for every `fetchNui` call:
|
|
106
147
|
|
|
107
148
|
```ts
|
|
108
149
|
const fetchNui = createFetchNui<MyEvents>({ debug: true });
|
|
109
150
|
|
|
110
151
|
await fetchNui("getPlayer", { id: 1 });
|
|
111
|
-
// Console:
|
|
112
152
|
// [NUIX] → getPlayer { id: 1 }
|
|
113
153
|
// [NUIX] ← getPlayer { name: "Laot", level: 42 }
|
|
114
154
|
```
|
|
115
155
|
|
|
116
|
-
###
|
|
156
|
+
### 8. Mock Data (Local Development)
|
|
117
157
|
|
|
118
|
-
|
|
158
|
+
Return pre-defined responses without real HTTP calls — useful when developing outside FiveM:
|
|
119
159
|
|
|
120
160
|
```ts
|
|
121
161
|
const fetchNui = createFetchNui<MyEvents>({
|
|
122
162
|
debug: true,
|
|
123
163
|
mockData: {
|
|
124
|
-
// Static response
|
|
125
164
|
getPlayer: { name: "DevPlayer", level: 99 },
|
|
126
|
-
|
|
127
|
-
// Dynamic response based on request
|
|
128
165
|
sendNotify: (req) => {
|
|
129
166
|
console.log("Mock notification:", req.message);
|
|
130
167
|
},
|
|
@@ -132,22 +169,24 @@ const fetchNui = createFetchNui<MyEvents>({
|
|
|
132
169
|
});
|
|
133
170
|
|
|
134
171
|
const player = await fetchNui("getPlayer", { id: 1 });
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
// player = { name: "DevPlayer", level: 99 }
|
|
172
|
+
// [NUIX] → getPlayer { id: 1 }
|
|
173
|
+
// [NUIX] ← getPlayer (mock) { name: "DevPlayer", level: 99 }
|
|
138
174
|
```
|
|
139
175
|
|
|
140
|
-
## Lua
|
|
176
|
+
## Lua Examples
|
|
141
177
|
|
|
142
178
|
```lua
|
|
143
|
-
--
|
|
179
|
+
-- NUI callback (responds to fetchNui calls)
|
|
144
180
|
RegisterNUICallback("getPlayer", function(data, cb)
|
|
145
181
|
local player = GetPlayerData(data.id)
|
|
146
182
|
cb({ name = player.name, level = player.level })
|
|
147
183
|
end)
|
|
148
184
|
|
|
149
|
-
--
|
|
185
|
+
-- Send messages to NUI
|
|
150
186
|
SendNUIMessage({ action = "showMenu", data = { items = {"Pistol", "Rifle"} } })
|
|
187
|
+
|
|
188
|
+
-- Send locales to NUI (for registerLocales)
|
|
189
|
+
SendNUIMessage({ action = "setLocales", data = Locales })
|
|
151
190
|
```
|
|
152
191
|
|
|
153
192
|
## API Reference
|
|
@@ -155,9 +194,13 @@ SendNUIMessage({ action = "showMenu", data = { items = {"Pistol", "Rifle"} } })
|
|
|
155
194
|
| Export | Type | Description |
|
|
156
195
|
|---|---|---|
|
|
157
196
|
| `createFetchNui<TMap>(options?)` | Factory | Returns a typed `fetchNui` function (supports debug & mock) |
|
|
158
|
-
| `onNuiMessage<TMap
|
|
197
|
+
| `onNuiMessage<TMap>(handler)` | Function | Single listener for all actions (switch-case) |
|
|
198
|
+
| `onNuiMessage<TMap, K>(action, handler)` | Function | Per-action listener with typed data |
|
|
159
199
|
| `luaFormat(template, ...args)` | Function | Lua-style `%s`/`%d`/`%f` formatter |
|
|
160
|
-
| `
|
|
200
|
+
| `registerLocales(locales)` | Function | Sets the global locale map at runtime |
|
|
201
|
+
| `extendLocales(...records)` | Function | Merges new entries into the global locale map |
|
|
202
|
+
| `_U(key, fallback, ...args)` | Function | Global translator — reads from registered locales |
|
|
203
|
+
| `createTranslator(options)` | Factory | Returns an isolated translator function |
|
|
161
204
|
| `mergeLocales(...records)` | Function | Deep-merges locale records |
|
|
162
205
|
|
|
163
206
|
## Build
|
package/dist/index.cjs
CHANGED
|
@@ -20,11 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
_U: () => _U,
|
|
23
24
|
createFetchNui: () => createFetchNui,
|
|
24
25
|
createTranslator: () => createTranslator,
|
|
26
|
+
extendLocales: () => extendLocales,
|
|
25
27
|
luaFormat: () => luaFormat,
|
|
26
28
|
mergeLocales: () => mergeLocales,
|
|
27
|
-
onNuiMessage: () => onNuiMessage
|
|
29
|
+
onNuiMessage: () => onNuiMessage,
|
|
30
|
+
registerLocales: () => registerLocales
|
|
28
31
|
});
|
|
29
32
|
module.exports = __toCommonJS(index_exports);
|
|
30
33
|
|
|
@@ -38,7 +41,8 @@ function getResourceName() {
|
|
|
38
41
|
function createFetchNui(factoryOptions) {
|
|
39
42
|
const debug = factoryOptions?.debug ?? false;
|
|
40
43
|
const mockData = factoryOptions?.mockData;
|
|
41
|
-
return async function fetchNui(event,
|
|
44
|
+
return async function fetchNui(event, ...args) {
|
|
45
|
+
const [data, options] = args;
|
|
42
46
|
if (debug) {
|
|
43
47
|
console.log(`[NUIX] \u2192 ${event}`, data ?? {});
|
|
44
48
|
}
|
|
@@ -86,12 +90,17 @@ function createFetchNui(factoryOptions) {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
// src/listener.ts
|
|
89
|
-
function onNuiMessage(
|
|
93
|
+
function onNuiMessage(actionOrHandler, handler) {
|
|
90
94
|
const listener = (event) => {
|
|
91
95
|
const payload = event.data;
|
|
92
96
|
if (!payload || typeof payload !== "object") return;
|
|
93
|
-
if (payload.action
|
|
94
|
-
|
|
97
|
+
if (!payload.action) return;
|
|
98
|
+
if (typeof actionOrHandler === "string") {
|
|
99
|
+
if (payload.action !== actionOrHandler) return;
|
|
100
|
+
handler(payload.data);
|
|
101
|
+
} else {
|
|
102
|
+
actionOrHandler(payload.action, payload.data);
|
|
103
|
+
}
|
|
95
104
|
};
|
|
96
105
|
window.addEventListener("message", listener);
|
|
97
106
|
return () => {
|
|
@@ -143,6 +152,18 @@ function createTranslator(options) {
|
|
|
143
152
|
return args.length > 0 ? luaFormat(template, ...args) : template;
|
|
144
153
|
};
|
|
145
154
|
}
|
|
155
|
+
var _locales = {};
|
|
156
|
+
function registerLocales(locales) {
|
|
157
|
+
_locales = locales;
|
|
158
|
+
}
|
|
159
|
+
function extendLocales(...records) {
|
|
160
|
+
_locales = mergeLocales(_locales, ...records);
|
|
161
|
+
}
|
|
162
|
+
function _U(key, fallback, ...args) {
|
|
163
|
+
const template = resolveKey(_locales, key);
|
|
164
|
+
if (template === void 0) return fallback;
|
|
165
|
+
return args.length > 0 ? luaFormat(template, ...args) : template;
|
|
166
|
+
}
|
|
146
167
|
function mergeLocales(...records) {
|
|
147
168
|
const result = {};
|
|
148
169
|
for (const record of records) {
|
|
@@ -159,10 +180,13 @@ function mergeLocales(...records) {
|
|
|
159
180
|
}
|
|
160
181
|
// Annotate the CommonJS export names for ESM import in node:
|
|
161
182
|
0 && (module.exports = {
|
|
183
|
+
_U,
|
|
162
184
|
createFetchNui,
|
|
163
185
|
createTranslator,
|
|
186
|
+
extendLocales,
|
|
164
187
|
luaFormat,
|
|
165
188
|
mergeLocales,
|
|
166
|
-
onNuiMessage
|
|
189
|
+
onNuiMessage,
|
|
190
|
+
registerLocales
|
|
167
191
|
});
|
|
168
192
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/listener.ts","../src/utils.ts"],"sourcesContent":["// ─── Types ───\r\nexport type {\r\n\tNuiEventMap,\r\n\tNuiMessagePayload,\r\n\tFetchNuiOptions,\r\n\tFetchNuiFactoryOptions,\r\n\tLocaleRecord,\r\n\tTranslatorOptions,\r\n\tTranslatorFn,\r\n\tFormatArg,\r\n\tUnsubscribeFn,\r\n\tNuiMessageHandler,\r\n} from \"./types\";\r\n\r\n// ─── Client ───\r\nexport { createFetchNui } from \"./client\";\r\n\r\n// ─── Listener ───\r\nexport { onNuiMessage } from \"./listener\";\r\n\r\n// ─── Utils ───\r\nexport { luaFormat, createTranslator, mergeLocales } from \"./utils\";\r\n","import type { NuiEventMap, FetchNuiOptions, FetchNuiFactoryOptions } from \"./types\";\r\n\r\n// ─── Resource Name ───\r\n\r\n/**\r\n * Grabs the resource name from FiveM's injected global.\r\n * Falls back to `\"nui-frame-app\"` when running outside the game (local dev, tests).\r\n */\r\nfunction getResourceName(): string {\r\n\tif (typeof window !== \"undefined\" && window.GetParentResourceName) {\r\n\t\treturn window.GetParentResourceName();\r\n\t}\r\n\treturn \"nui-frame-app\";\r\n}\r\n\r\n// ─── FetchNui Factory ───\r\n\r\n/**\r\n * Creates a fully typed `fetchNui` function tied to your event map.\r\n *\r\n * The returned function POSTs JSON to `https://<resourceName>/<event>`,\r\n * which maps 1:1 to `RegisterNUICallback` on the Lua side.\r\n *\r\n * Supports optional debug logging, mock data for local dev, and per-call timeout.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyEvents extends NuiEventMap {\r\n * getPlayer: { request: { id: number }; response: { name: string } };\r\n * notify: { request: { msg: string }; response: void };\r\n * }\r\n *\r\n * // Production usage\r\n * const fetchNui = createFetchNui<MyEvents>();\r\n * const player = await fetchNui(\"getPlayer\", { id: 1 });\r\n * // player.name is typed as string\r\n *\r\n * // Local dev with mocks + debug\r\n * const fetchNui = createFetchNui<MyEvents>({\r\n * debug: true,\r\n * mockData: {\r\n * getPlayer: { name: \"DevPlayer\" },\r\n * notify: (req) => { console.log(\"Mock:\", req.msg); },\r\n * },\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * RegisterNUICallback(\"getPlayer\", function(data, cb)\r\n * local player = GetPlayerData(data.id)\r\n * cb({ name = player.name })\r\n * end)\r\n * ```\r\n */\r\nexport function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>) {\r\n\tconst debug = factoryOptions?.debug ?? false;\r\n\tconst mockData = factoryOptions?.mockData;\r\n\r\n\treturn async function fetchNui<K extends keyof TMap & string>(\r\n\t\tevent: K,\r\n\t\tdata?: TMap[K][\"request\"],\r\n\t\toptions?: FetchNuiOptions,\r\n\t): Promise<TMap[K][\"response\"]> {\r\n\t\tif (debug) {\r\n\t\t\tconsole.log(`[NUIX] → ${event}`, data ?? {});\r\n\t\t}\r\n\r\n\t\t// ─── Mock Mode ───\r\n\r\n\t\tif (mockData && event in mockData) {\r\n\t\t\tconst mock = mockData[event];\r\n\r\n\t\t\tif (mock === undefined) {\r\n\t\t\t\tthrow new Error(`[NUIX] Mock data for \"${event}\" is undefined`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result =\r\n\t\t\t\ttypeof mock === \"function\"\r\n\t\t\t\t\t? (mock as (req: TMap[K][\"request\"]) => TMap[K][\"response\"])(data as TMap[K][\"request\"])\r\n\t\t\t\t\t: mock;\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event} (mock)`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result as TMap[K][\"response\"];\r\n\t\t}\r\n\r\n\t\t// ─── Real Fetch ───\r\n\r\n\t\tconst url = `https://${getResourceName()}/${event}`;\r\n\r\n\t\tconst controller = options?.timeout ? new AbortController() : undefined;\r\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\r\n\r\n\t\tif (controller && options?.timeout) {\r\n\t\t\ttimeoutId = setTimeout(() => controller.abort(), options.timeout);\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst response = await fetch(url, {\r\n\t\t\t\tmethod: \"POST\",\r\n\t\t\t\theaders: { \"Content-Type\": \"application/json; charset=UTF-8\" },\r\n\t\t\t\tbody: JSON.stringify(data ?? {}),\r\n\t\t\t\tsignal: controller?.signal,\r\n\t\t\t});\r\n\r\n\t\t\tif (!response.ok) {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") failed with HTTP ${response.status}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result = (await response.json()) as TMap[K][\"response\"];\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event}`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") timed out after ${options?.timeout}ms`);\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t} finally {\r\n\t\t\tif (timeoutId !== undefined) clearTimeout(timeoutId);\r\n\t\t}\r\n\t};\r\n}\r\n","import type { NuiEventMap, NuiMessagePayload, NuiMessageHandler, UnsubscribeFn } from \"./types\";\r\n\r\n// ─── NUI Message Listener ───\r\n\r\n/**\r\n * Listens for NUI messages from Lua (`SendNUIMessage`), filtered by action name.\r\n * Only messages matching the given `action` trigger the handler.\r\n * Returns a cleanup function to stop listening.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyMessages extends NuiEventMap {\r\n * showMenu: { request: { items: string[] }; response: void };\r\n * hideMenu: { request: void; response: void };\r\n * }\r\n *\r\n * const unsub = onNuiMessage<MyMessages, \"showMenu\">(\"showMenu\", (data) => {\r\n * console.log(data.items); // typed as string[]\r\n * });\r\n *\r\n * // Stop listening when done\r\n * unsub();\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * SendNUIMessage({ action = \"showMenu\", data = { items = {\"Pistol\", \"Rifle\"} } })\r\n * ```\r\n */\r\nexport function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(\r\n\taction: K,\r\n\thandler: NuiMessageHandler<TMap[K][\"request\"]>,\r\n): UnsubscribeFn {\r\n\tconst listener = (event: MessageEvent<NuiMessagePayload<TMap[K][\"request\"]>>) => {\r\n\t\tconst payload = event.data;\r\n\r\n\t\tif (!payload || typeof payload !== \"object\") return;\r\n\t\tif (payload.action !== action) return;\r\n\r\n\t\thandler(payload.data);\r\n\t};\r\n\r\n\twindow.addEventListener(\"message\", listener);\r\n\r\n\treturn () => {\r\n\t\twindow.removeEventListener(\"message\", listener);\r\n\t};\r\n}\r\n","import type { FormatArg, LocaleRecord, TranslatorFn, TranslatorOptions } from \"./types\";\r\n\r\n// ─── Lua-Style Formatter ───\r\n\r\n/**\r\n * Formats a string using Lua-style placeholders.\r\n *\r\n * Specifiers:\r\n * - `%s` — string (null/undefined becomes empty string)\r\n * - `%d` — integer (floors the value, NaN becomes 0)\r\n * - `%f` — float (NaN becomes 0)\r\n * - `%%` — literal percent sign\r\n *\r\n * @example\r\n * ```ts\r\n * luaFormat(\"Hello %s, you are level %d\", \"Laot\", 42);\r\n * // → \"Hello Laot, you are level 42\"\r\n *\r\n * luaFormat(\"Accuracy: %f%%\", 99.5);\r\n * // → \"Accuracy: 99.5%\"\r\n *\r\n * luaFormat(\"Safe: %s %d\", undefined, NaN);\r\n * // → \"Safe: 0\"\r\n * ```\r\n */\r\nexport function luaFormat(template: string, ...args: FormatArg[]): string {\r\n\tlet argIndex = 0;\r\n\r\n\treturn template.replace(/%([sdfi%])/g, (match, specifier: string) => {\r\n\t\tif (specifier === \"%\") return \"%\";\r\n\r\n\t\tconst raw = args[argIndex++];\r\n\r\n\t\tswitch (specifier) {\r\n\t\t\tcase \"s\":\r\n\t\t\t\treturn String(raw ?? \"\");\r\n\r\n\t\t\tcase \"d\":\r\n\t\t\tcase \"i\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : Math.floor(num));\r\n\t\t\t}\r\n\r\n\t\t\tcase \"f\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : num);\r\n\t\t\t}\r\n\r\n\t\t\tdefault:\r\n\t\t\t\treturn match;\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// ─── Dot-Notation Resolver ───\r\n\r\n/**\r\n * Walks a nested locale object by splitting the key on `.`\r\n * Returns the string value at the end of the path, or `undefined` if any segment is missing.\r\n *\r\n * `\"client.greeting\"` → looks up `locales.client.greeting`\r\n * `\"flat_key\"` → looks up `locales.flat_key` directly\r\n */\r\nfunction resolveKey(locales: LocaleRecord, key: string): string | undefined {\r\n\tconst segments = key.split(\".\");\r\n\tlet current: LocaleRecord | string = locales;\r\n\r\n\tfor (const segment of segments) {\r\n\t\tif (typeof current !== \"object\" || current === null) return undefined;\r\n\t\tconst next: string | LocaleRecord | undefined = current[segment];\r\n\t\tif (next === undefined) return undefined;\r\n\t\tcurrent = next;\r\n\t}\r\n\r\n\treturn typeof current === \"string\" ? current : undefined;\r\n}\r\n\r\n// ─── Translator Factory ───\r\n\r\n/**\r\n * Creates a `_U(key, fallback, ...args)` translator bound to a locale record.\r\n *\r\n * The key supports dot-notation to traverse nested locale objects.\r\n * If the key isn't found, the fallback string is returned as-is.\r\n * Any extra args are passed through `luaFormat` for placeholder substitution.\r\n *\r\n * @example\r\n * ```ts\r\n * const _U = createTranslator({\r\n * locales: {\r\n * client: {\r\n * greeting: \"Hello %s!\",\r\n * level: \"Level %d\",\r\n * },\r\n * server: {\r\n * error: \"Error: %s\",\r\n * },\r\n * flat_key: \"Plain message\",\r\n * },\r\n * });\r\n *\r\n * _U(\"client.greeting\", \"MISSING\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"client.level\", \"MISSING\", 42); // → \"Level 42\"\r\n * _U(\"flat_key\", \"MISSING\"); // → \"Plain message\"\r\n * _U(\"no.key\", \"Not found\"); // → \"Not found\"\r\n * ```\r\n */\r\nexport function createTranslator(options: TranslatorOptions): TranslatorFn {\r\n\tconst { locales } = options;\r\n\r\n\treturn (key: string, fallback: string, ...args: FormatArg[]): string => {\r\n\t\tconst template = resolveKey(locales, key);\r\n\r\n\t\tif (template === undefined) {\r\n\t\t\treturn fallback;\r\n\t\t}\r\n\r\n\t\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n\t};\r\n}\r\n\r\n// ─── Deep Merge ───\r\n\r\n/**\r\n * Deep-merges multiple locale records into one.\r\n * Later records override earlier ones on key conflicts.\r\n * Nested objects are merged recursively, not replaced entirely.\r\n *\r\n * @example\r\n * ```ts\r\n * const base = { client: { greeting: \"Hello %s!\", level: \"Level %d\" } };\r\n * const overrides = { client: { greeting: \"Hey %s!\" } };\r\n *\r\n * const merged = mergeLocales(base, overrides);\r\n * // merged.client.greeting → \"Hey %s!\"\r\n * // merged.client.level → \"Level %d\" (preserved from base)\r\n * ```\r\n */\r\nexport function mergeLocales(...records: LocaleRecord[]): LocaleRecord {\r\n\tconst result: LocaleRecord = {};\r\n\r\n\tfor (const record of records) {\r\n\t\tfor (const [key, value] of Object.entries(record)) {\r\n\t\t\tconst existing = result[key];\r\n\r\n\t\t\tif (typeof value === \"object\" && typeof existing === \"object\" && existing !== undefined) {\r\n\t\t\t\tresult[key] = mergeLocales(existing, value);\r\n\t\t\t} else {\r\n\t\t\t\tresult[key] = value;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/listener.ts","../src/utils.ts"],"sourcesContent":["// ─── Types ───\r\nexport type {\r\n\tNuiEventMap,\r\n\tNuiMessagePayload,\r\n\tFetchNuiOptions,\r\n\tFetchNuiFactoryOptions,\r\n\tLocaleRecord,\r\n\tTranslatorOptions,\r\n\tTranslatorFn,\r\n\tFormatArg,\r\n\tUnsubscribeFn,\r\n\tNuiMessageHandler,\r\n} from \"./types\";\r\n\r\n// ─── Client ───\r\nexport { createFetchNui } from \"./client\";\r\n\r\n// ─── Listener ───\r\nexport { onNuiMessage } from \"./listener\";\r\n\r\n// ─── Utils ───\r\nexport { luaFormat, createTranslator, mergeLocales, registerLocales, extendLocales, _U } from \"./utils\";\r\n","import type { NuiEventMap, FetchNuiOptions, FetchNuiFactoryOptions } from \"./types\";\r\n\r\n// ─── Resource Name ───\r\n\r\n/**\r\n * Grabs the resource name from FiveM's injected global.\r\n * Falls back to `\"nui-frame-app\"` when running outside the game (local dev, tests).\r\n */\r\nfunction getResourceName(): string {\r\n\tif (typeof window !== \"undefined\" && window.GetParentResourceName) {\r\n\t\treturn window.GetParentResourceName();\r\n\t}\r\n\treturn \"nui-frame-app\";\r\n}\r\n\r\n// ─── FetchNui Factory ───\r\n\r\n/**\r\n * Creates a fully typed `fetchNui` function tied to your event map.\r\n *\r\n * The returned function POSTs JSON to `https://<resourceName>/<event>`,\r\n * which maps 1:1 to `RegisterNUICallback` on the Lua side.\r\n *\r\n * Supports optional debug logging, mock data for local dev, and per-call timeout.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyEvents extends NuiEventMap {\r\n * getPlayer: { request: { id: number }; response: { name: string } };\r\n * notify: { request: { msg: string }; response: void };\r\n * }\r\n *\r\n * // Production usage\r\n * const fetchNui = createFetchNui<MyEvents>();\r\n * const player = await fetchNui(\"getPlayer\", { id: 1 });\r\n * // player.name is typed as string\r\n *\r\n * // Local dev with mocks + debug\r\n * const fetchNui = createFetchNui<MyEvents>({\r\n * debug: true,\r\n * mockData: {\r\n * getPlayer: { name: \"DevPlayer\" },\r\n * notify: (req) => { console.log(\"Mock:\", req.msg); },\r\n * },\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * RegisterNUICallback(\"getPlayer\", function(data, cb)\r\n * local player = GetPlayerData(data.id)\r\n * cb({ name = player.name })\r\n * end)\r\n * ```\r\n */\r\nexport function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>) {\r\n\tconst debug = factoryOptions?.debug ?? false;\r\n\tconst mockData = factoryOptions?.mockData;\r\n\r\n\treturn async function fetchNui<K extends keyof TMap & string>(\r\n\t\tevent: K,\r\n\t\t...args: TMap[K][\"request\"] extends void\r\n\t\t\t? [data?: TMap[K][\"request\"], options?: FetchNuiOptions]\r\n\t\t\t: [data: TMap[K][\"request\"], options?: FetchNuiOptions]\r\n\t): Promise<TMap[K][\"response\"]> {\r\n\t\tconst [data, options] = args;\r\n\t\tif (debug) {\r\n\t\t\tconsole.log(`[NUIX] → ${event}`, data ?? {});\r\n\t\t}\r\n\r\n\t\t// ─── Mock Mode ───\r\n\r\n\t\tif (mockData && event in mockData) {\r\n\t\t\tconst mock = mockData[event];\r\n\r\n\t\t\tif (mock === undefined) {\r\n\t\t\t\tthrow new Error(`[NUIX] Mock data for \"${event}\" is undefined`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result =\r\n\t\t\t\ttypeof mock === \"function\"\r\n\t\t\t\t\t? (mock as (req: TMap[K][\"request\"]) => TMap[K][\"response\"])(data as TMap[K][\"request\"])\r\n\t\t\t\t\t: mock;\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event} (mock)`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result as TMap[K][\"response\"];\r\n\t\t}\r\n\r\n\t\t// ─── Real Fetch ───\r\n\r\n\t\tconst url = `https://${getResourceName()}/${event}`;\r\n\r\n\t\tconst controller = options?.timeout ? new AbortController() : undefined;\r\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\r\n\r\n\t\tif (controller && options?.timeout) {\r\n\t\t\ttimeoutId = setTimeout(() => controller.abort(), options.timeout);\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst response = await fetch(url, {\r\n\t\t\t\tmethod: \"POST\",\r\n\t\t\t\theaders: { \"Content-Type\": \"application/json; charset=UTF-8\" },\r\n\t\t\t\tbody: JSON.stringify(data ?? {}),\r\n\t\t\t\tsignal: controller?.signal,\r\n\t\t\t});\r\n\r\n\t\t\tif (!response.ok) {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") failed with HTTP ${response.status}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result = (await response.json()) as TMap[K][\"response\"];\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event}`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") timed out after ${options?.timeout}ms`);\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t} finally {\r\n\t\t\tif (timeoutId !== undefined) clearTimeout(timeoutId);\r\n\t\t}\r\n\t};\r\n}\r\n","import type { NuiEventMap, NuiMessagePayload, NuiMessageHandler, UnsubscribeFn } from \"./types\";\r\n\r\n// ─── NUI Message Listener ───\r\n\r\n/**\r\n * Listens for NUI messages from Lua (`SendNUIMessage`).\r\n *\r\n * **Per-action** — filters by action name, `data` is fully typed:\r\n * ```ts\r\n * onNuiMessage<Events, \"showMenu\">(\"showMenu\", (data) => {\r\n * console.log(data.items); // ✅ typed as string[]\r\n * });\r\n * ```\r\n *\r\n * **Switch-case** — single listener for all actions:\r\n * ```ts\r\n * onNuiMessage<Events>((action, data) => {\r\n * switch (action) {\r\n * case \"setLocales\":\r\n * registerLocales(data);\r\n * break;\r\n * case \"showMenu\":\r\n * openMenu(data.items);\r\n * break;\r\n * }\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * SendNUIMessage({ action = \"showMenu\", data = { items = {\"Pistol\", \"Rifle\"} } })\r\n * ```\r\n */\r\n\r\n// Per-action overload\r\nexport function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(\r\n\taction: K,\r\n\thandler: NuiMessageHandler<TMap[K][\"request\"]>,\r\n): UnsubscribeFn;\r\n\r\n// Switch-case overload\r\nexport function onNuiMessage<TMap extends NuiEventMap>(\r\n\thandler: (action: keyof TMap & string, data: any) => void,\r\n): UnsubscribeFn;\r\n\r\n// Implementation\r\nexport function onNuiMessage(\r\n\tactionOrHandler: string | ((action: string, data: unknown) => void),\r\n\thandler?: (data: unknown) => void,\r\n): UnsubscribeFn {\r\n\tconst listener = (event: MessageEvent<NuiMessagePayload>) => {\r\n\t\tconst payload = event.data;\r\n\r\n\t\tif (!payload || typeof payload !== \"object\") return;\r\n\t\tif (!payload.action) return;\r\n\r\n\t\tif (typeof actionOrHandler === \"string\") {\r\n\t\t\tif (payload.action !== actionOrHandler) return;\r\n\t\t\thandler!(payload.data);\r\n\t\t} else {\r\n\t\t\tactionOrHandler(payload.action, payload.data);\r\n\t\t}\r\n\t};\r\n\r\n\twindow.addEventListener(\"message\", listener);\r\n\r\n\treturn () => {\r\n\t\twindow.removeEventListener(\"message\", listener);\r\n\t};\r\n}\r\n","import type { FormatArg, LocaleRecord, TranslatorFn, TranslatorOptions } from \"./types\";\r\n\r\n// ─── Lua-Style Formatter ───\r\n\r\n/**\r\n * Formats a string using Lua-style placeholders.\r\n *\r\n * Specifiers:\r\n * - `%s` — string (null/undefined becomes empty string)\r\n * - `%d` — integer (floors the value, NaN becomes 0)\r\n * - `%f` — float (NaN becomes 0)\r\n * - `%%` — literal percent sign\r\n *\r\n * @example\r\n * ```ts\r\n * luaFormat(\"Hello %s, you are level %d\", \"Laot\", 42);\r\n * // → \"Hello Laot, you are level 42\"\r\n *\r\n * luaFormat(\"Accuracy: %f%%\", 99.5);\r\n * // → \"Accuracy: 99.5%\"\r\n *\r\n * luaFormat(\"Safe: %s %d\", undefined, NaN);\r\n * // → \"Safe: 0\"\r\n * ```\r\n */\r\nexport function luaFormat(template: string, ...args: FormatArg[]): string {\r\n\tlet argIndex = 0;\r\n\r\n\treturn template.replace(/%([sdfi%])/g, (match, specifier: string) => {\r\n\t\tif (specifier === \"%\") return \"%\";\r\n\r\n\t\tconst raw = args[argIndex++];\r\n\r\n\t\tswitch (specifier) {\r\n\t\t\tcase \"s\":\r\n\t\t\t\treturn String(raw ?? \"\");\r\n\r\n\t\t\tcase \"d\":\r\n\t\t\tcase \"i\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : Math.floor(num));\r\n\t\t\t}\r\n\r\n\t\t\tcase \"f\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : num);\r\n\t\t\t}\r\n\r\n\t\t\tdefault:\r\n\t\t\t\treturn match;\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// ─── Dot-Notation Resolver ───\r\n\r\n/**\r\n * Walks a nested locale object by splitting the key on `.`\r\n * Returns the string value at the end of the path, or `undefined` if any segment is missing.\r\n *\r\n * `\"client.greeting\"` → looks up `locales.client.greeting`\r\n * `\"flat_key\"` → looks up `locales.flat_key` directly\r\n */\r\nfunction resolveKey(locales: LocaleRecord, key: string): string | undefined {\r\n\tconst segments = key.split(\".\");\r\n\tlet current: LocaleRecord | string = locales;\r\n\r\n\tfor (const segment of segments) {\r\n\t\tif (typeof current !== \"object\" || current === null) return undefined;\r\n\t\tconst next: string | LocaleRecord | undefined = current[segment];\r\n\t\tif (next === undefined) return undefined;\r\n\t\tcurrent = next;\r\n\t}\r\n\r\n\treturn typeof current === \"string\" ? current : undefined;\r\n}\r\n\r\n// ─── Translator Factory ───\r\n\r\n/**\r\n * Creates a `_U(key, fallback, ...args)` translator bound to a locale record.\r\n *\r\n * The key supports dot-notation to traverse nested locale objects.\r\n * If the key isn't found, the fallback string is returned as-is.\r\n * Any extra args are passed through `luaFormat` for placeholder substitution.\r\n *\r\n * @example\r\n * ```ts\r\n * const _U = createTranslator({\r\n * locales: {\r\n * client: {\r\n * greeting: \"Hello %s!\",\r\n * level: \"Level %d\",\r\n * },\r\n * server: {\r\n * error: \"Error: %s\",\r\n * },\r\n * flat_key: \"Plain message\",\r\n * },\r\n * });\r\n *\r\n * _U(\"client.greeting\", \"MISSING\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"client.level\", \"MISSING\", 42); // → \"Level 42\"\r\n * _U(\"flat_key\", \"MISSING\"); // → \"Plain message\"\r\n * _U(\"no.key\", \"Not found\"); // → \"Not found\"\r\n * ```\r\n */\r\nexport function createTranslator(options: TranslatorOptions): TranslatorFn {\r\n\tconst { locales } = options;\r\n\r\n\treturn (key: string, fallback: string, ...args: FormatArg[]): string => {\r\n\t\tconst template = resolveKey(locales, key);\r\n\r\n\t\tif (template === undefined) {\r\n\t\t\treturn fallback;\r\n\t\t}\r\n\r\n\t\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n\t};\r\n}\r\n\r\n// ─── Global Locale Registry ───\r\n\r\nlet _locales: LocaleRecord = {};\r\n\r\n/**\r\n * Sets the global locale map. Call this when Lua sends locale data to the NUI.\r\n *\r\n * @example\r\n * ```ts\r\n * // Lua side:\r\n * // SendNUIMessage({ action = \"setLocales\", data = locales })\r\n *\r\n * onNuiMessage<Events>((action, data) => {\r\n * switch (action) {\r\n * case \"setLocales\":\r\n * registerLocales(data);\r\n * break;\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function registerLocales(locales: LocaleRecord): void {\r\n\t_locales = locales;\r\n}\r\n\r\n/**\r\n * Merges new entries into the current global locale map without replacing it.\r\n *\r\n * @example\r\n * ```ts\r\n * registerLocales({ ui: { title: \"Dashboard\" } });\r\n * extendLocales({ ui: { subtitle: \"Overview\" } });\r\n *\r\n * _U(\"ui.title\", \"\"); // → \"Dashboard\"\r\n * _U(\"ui.subtitle\", \"\"); // → \"Overview\"\r\n * ```\r\n */\r\nexport function extendLocales(...records: LocaleRecord[]): void {\r\n\t_locales = mergeLocales(_locales, ...records);\r\n}\r\n\r\n/**\r\n * Global translator — reads from the locale map set by `registerLocales` / `extendLocales`.\r\n *\r\n * @param key Dot-notated key, e.g. `\"ui.greeting\"` or flat `\"title\"`\r\n * @param fallback Returned as-is when the key doesn't exist\r\n * @param args Values for `%s`, `%d`, `%f` placeholders\r\n *\r\n * @example\r\n * ```ts\r\n * import { registerLocales, _U } from \"nuix\";\r\n *\r\n * // After Lua sends locales:\r\n * // { ui: { greeting: \"Hello %s!\", level: \"Level %d\" } }\r\n *\r\n * _U(\"ui.greeting\", \"Hi\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"ui.level\", \"Lv.\", 42); // → \"Level 42\"\r\n * _U(\"missing.key\", \"Fallback\"); // → \"Fallback\"\r\n * ```\r\n */\r\nexport function _U(key: string, fallback: string, ...args: FormatArg[]): string {\r\n\tconst template = resolveKey(_locales, key);\r\n\tif (template === undefined) return fallback;\r\n\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n}\r\n\r\n// ─── Deep Merge ───\r\n\r\n/**\r\n * Deep-merges multiple locale records into one.\r\n * Later records override earlier ones on key conflicts.\r\n * Nested objects are merged recursively, not replaced entirely.\r\n *\r\n * @example\r\n * ```ts\r\n * const base = { client: { greeting: \"Hello %s!\", level: \"Level %d\" } };\r\n * const overrides = { client: { greeting: \"Hey %s!\" } };\r\n *\r\n * const merged = mergeLocales(base, overrides);\r\n * // merged.client.greeting → \"Hey %s!\"\r\n * // merged.client.level → \"Level %d\" (preserved from base)\r\n * ```\r\n */\r\nexport function mergeLocales(...records: LocaleRecord[]): LocaleRecord {\r\n\tconst result: LocaleRecord = {};\r\n\r\n\tfor (const record of records) {\r\n\t\tfor (const [key, value] of Object.entries(record)) {\r\n\t\t\tconst existing = result[key];\r\n\r\n\t\t\tif (typeof value === \"object\" && typeof existing === \"object\" && existing !== undefined) {\r\n\t\t\t\tresult[key] = mergeLocales(existing, value);\r\n\t\t\t} else {\r\n\t\t\t\tresult[key] = value;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,SAAS,kBAA0B;AAClC,MAAI,OAAO,WAAW,eAAe,OAAO,uBAAuB;AAClE,WAAO,OAAO,sBAAsB;AAAA,EACrC;AACA,SAAO;AACR;AA0CO,SAAS,eAAyC,gBAA+C;AACvG,QAAM,QAAQ,gBAAgB,SAAS;AACvC,QAAM,WAAW,gBAAgB;AAEjC,SAAO,eAAe,SACrB,UACG,MAG4B;AAC/B,UAAM,CAAC,MAAM,OAAO,IAAI;AACxB,QAAI,OAAO;AACV,cAAQ,IAAI,iBAAY,KAAK,IAAI,QAAQ,CAAC,CAAC;AAAA,IAC5C;AAIA,QAAI,YAAY,SAAS,UAAU;AAClC,YAAM,OAAO,SAAS,KAAK;AAE3B,UAAI,SAAS,QAAW;AACvB,cAAM,IAAI,MAAM,yBAAyB,KAAK,gBAAgB;AAAA,MAC/D;AAEA,YAAM,SACL,OAAO,SAAS,aACZ,KAA0D,IAA0B,IACrF;AAEJ,UAAI,OAAO;AACV,gBAAQ,IAAI,iBAAY,KAAK,WAAW,MAAM;AAAA,MAC/C;AAEA,aAAO;AAAA,IACR;AAIA,UAAM,MAAM,WAAW,gBAAgB,CAAC,IAAI,KAAK;AAEjD,UAAM,aAAa,SAAS,UAAU,IAAI,gBAAgB,IAAI;AAC9D,QAAI;AAEJ,QAAI,cAAc,SAAS,SAAS;AACnC,kBAAY,WAAW,MAAM,WAAW,MAAM,GAAG,QAAQ,OAAO;AAAA,IACjE;AAEA,QAAI;AACH,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QACjC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,kCAAkC;AAAA,QAC7D,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC;AAAA,QAC/B,QAAQ,YAAY;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACjB,cAAM,IAAI,MAAM,oBAAoB,KAAK,uBAAuB,SAAS,MAAM,EAAE;AAAA,MAClF;AAEA,YAAM,SAAU,MAAM,SAAS,KAAK;AAEpC,UAAI,OAAO;AACV,gBAAQ,IAAI,iBAAY,KAAK,IAAI,MAAM;AAAA,MACxC;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AACf,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AACjE,cAAM,IAAI,MAAM,oBAAoB,KAAK,sBAAsB,SAAS,OAAO,IAAI;AAAA,MACpF;AACA,YAAM;AAAA,IACP,UAAE;AACD,UAAI,cAAc,OAAW,cAAa,SAAS;AAAA,IACpD;AAAA,EACD;AACD;;;ACpFO,SAAS,aACf,iBACA,SACgB;AAChB,QAAM,WAAW,CAAC,UAA2C;AAC5D,UAAM,UAAU,MAAM;AAEtB,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU;AAC7C,QAAI,CAAC,QAAQ,OAAQ;AAErB,QAAI,OAAO,oBAAoB,UAAU;AACxC,UAAI,QAAQ,WAAW,gBAAiB;AACxC,cAAS,QAAQ,IAAI;AAAA,IACtB,OAAO;AACN,sBAAgB,QAAQ,QAAQ,QAAQ,IAAI;AAAA,IAC7C;AAAA,EACD;AAEA,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,SAAO,MAAM;AACZ,WAAO,oBAAoB,WAAW,QAAQ;AAAA,EAC/C;AACD;;;AC5CO,SAAS,UAAU,aAAqB,MAA2B;AACzE,MAAI,WAAW;AAEf,SAAO,SAAS,QAAQ,eAAe,CAAC,OAAO,cAAsB;AACpE,QAAI,cAAc,IAAK,QAAO;AAE9B,UAAM,MAAM,KAAK,UAAU;AAE3B,YAAQ,WAAW;AAAA,MAClB,KAAK;AACJ,eAAO,OAAO,OAAO,EAAE;AAAA,MAExB,KAAK;AAAA,MACL,KAAK,KAAK;AACT,cAAM,MAAM,OAAO,GAAG;AACtB,eAAO,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC;AAAA,MACtD;AAAA,MAEA,KAAK,KAAK;AACT,cAAM,MAAM,OAAO,GAAG;AACtB,eAAO,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,GAAG;AAAA,MAC1C;AAAA,MAEA;AACC,eAAO;AAAA,IACT;AAAA,EACD,CAAC;AACF;AAWA,SAAS,WAAW,SAAuB,KAAiC;AAC3E,QAAM,WAAW,IAAI,MAAM,GAAG;AAC9B,MAAI,UAAiC;AAErC,aAAW,WAAW,UAAU;AAC/B,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;AAC5D,UAAM,OAA0C,QAAQ,OAAO;AAC/D,QAAI,SAAS,OAAW,QAAO;AAC/B,cAAU;AAAA,EACX;AAEA,SAAO,OAAO,YAAY,WAAW,UAAU;AAChD;AAgCO,SAAS,iBAAiB,SAA0C;AAC1E,QAAM,EAAE,QAAQ,IAAI;AAEpB,SAAO,CAAC,KAAa,aAAqB,SAA8B;AACvE,UAAM,WAAW,WAAW,SAAS,GAAG;AAExC,QAAI,aAAa,QAAW;AAC3B,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,SAAS,IAAI,UAAU,UAAU,GAAG,IAAI,IAAI;AAAA,EACzD;AACD;AAIA,IAAI,WAAyB,CAAC;AAmBvB,SAAS,gBAAgB,SAA6B;AAC5D,aAAW;AACZ;AAcO,SAAS,iBAAiB,SAA+B;AAC/D,aAAW,aAAa,UAAU,GAAG,OAAO;AAC7C;AAqBO,SAAS,GAAG,KAAa,aAAqB,MAA2B;AAC/E,QAAM,WAAW,WAAW,UAAU,GAAG;AACzC,MAAI,aAAa,OAAW,QAAO;AACnC,SAAO,KAAK,SAAS,IAAI,UAAU,UAAU,GAAG,IAAI,IAAI;AACzD;AAmBO,SAAS,gBAAgB,SAAuC;AACtE,QAAM,SAAuB,CAAC;AAE9B,aAAW,UAAU,SAAS;AAC7B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,YAAM,WAAW,OAAO,GAAG;AAE3B,UAAI,OAAO,UAAU,YAAY,OAAO,aAAa,YAAY,aAAa,QAAW;AACxF,eAAO,GAAG,IAAI,aAAa,UAAU,KAAK;AAAA,MAC3C,OAAO;AACN,eAAO,GAAG,IAAI;AAAA,MACf;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -87,16 +87,16 @@ interface TranslatorOptions {
|
|
|
87
87
|
locales: LocaleRecord;
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Translator function
|
|
90
|
+
* Translator function signature used by both `createTranslator` and the global `_U`.
|
|
91
91
|
*
|
|
92
|
-
* @param key
|
|
93
|
-
* @param fallback
|
|
94
|
-
* @param args
|
|
92
|
+
* @param key Dot-notated key like `"ui.greeting"` or flat like `"title"`
|
|
93
|
+
* @param fallback Returned when the key doesn't exist in the locale map
|
|
94
|
+
* @param args Format arguments for `%s`, `%d`, `%f` placeholders
|
|
95
95
|
*
|
|
96
96
|
* @example
|
|
97
97
|
* ```ts
|
|
98
|
-
* _U("
|
|
99
|
-
* _U("
|
|
98
|
+
* _U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
99
|
+
* _U("missing.key", "Fallback"); // → "Fallback"
|
|
100
100
|
* ```
|
|
101
101
|
*/
|
|
102
102
|
type TranslatorFn = (key: string, fallback: string, ...args: FormatArg[]) => string;
|
|
@@ -148,26 +148,30 @@ type NuiMessageHandler<TData = unknown> = (data: TData) => void;
|
|
|
148
148
|
* end)
|
|
149
149
|
* ```
|
|
150
150
|
*/
|
|
151
|
-
declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>): <K extends keyof TMap & string>(event: K, data?: TMap[K]["request"], options?: FetchNuiOptions) => Promise<TMap[K]["response"]>;
|
|
151
|
+
declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>): <K extends keyof TMap & string>(event: K, ...args: TMap[K]["request"] extends void ? [data?: TMap[K]["request"], options?: FetchNuiOptions] : [data: TMap[K]["request"], options?: FetchNuiOptions]) => Promise<TMap[K]["response"]>;
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Listens for NUI messages from Lua (`SendNUIMessage`)
|
|
155
|
-
* Only messages matching the given `action` trigger the handler.
|
|
156
|
-
* Returns a cleanup function to stop listening.
|
|
154
|
+
* Listens for NUI messages from Lua (`SendNUIMessage`).
|
|
157
155
|
*
|
|
158
|
-
*
|
|
156
|
+
* **Per-action** — filters by action name, `data` is fully typed:
|
|
159
157
|
* ```ts
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* hideMenu: { request: void; response: void };
|
|
163
|
-
* }
|
|
164
|
-
*
|
|
165
|
-
* const unsub = onNuiMessage<MyMessages, "showMenu">("showMenu", (data) => {
|
|
166
|
-
* console.log(data.items); // typed as string[]
|
|
158
|
+
* onNuiMessage<Events, "showMenu">("showMenu", (data) => {
|
|
159
|
+
* console.log(data.items); // ✅ typed as string[]
|
|
167
160
|
* });
|
|
161
|
+
* ```
|
|
168
162
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
163
|
+
* **Switch-case** — single listener for all actions:
|
|
164
|
+
* ```ts
|
|
165
|
+
* onNuiMessage<Events>((action, data) => {
|
|
166
|
+
* switch (action) {
|
|
167
|
+
* case "setLocales":
|
|
168
|
+
* registerLocales(data);
|
|
169
|
+
* break;
|
|
170
|
+
* case "showMenu":
|
|
171
|
+
* openMenu(data.items);
|
|
172
|
+
* break;
|
|
173
|
+
* }
|
|
174
|
+
* });
|
|
171
175
|
* ```
|
|
172
176
|
*
|
|
173
177
|
* Lua side:
|
|
@@ -176,6 +180,7 @@ declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: Fetch
|
|
|
176
180
|
* ```
|
|
177
181
|
*/
|
|
178
182
|
declare function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(action: K, handler: NuiMessageHandler<TMap[K]["request"]>): UnsubscribeFn;
|
|
183
|
+
declare function onNuiMessage<TMap extends NuiEventMap>(handler: (action: keyof TMap & string, data: any) => void): UnsubscribeFn;
|
|
179
184
|
|
|
180
185
|
/**
|
|
181
186
|
* Formats a string using Lua-style placeholders.
|
|
@@ -228,6 +233,57 @@ declare function luaFormat(template: string, ...args: FormatArg[]): string;
|
|
|
228
233
|
* ```
|
|
229
234
|
*/
|
|
230
235
|
declare function createTranslator(options: TranslatorOptions): TranslatorFn;
|
|
236
|
+
/**
|
|
237
|
+
* Sets the global locale map. Call this when Lua sends locale data to the NUI.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* // Lua side:
|
|
242
|
+
* // SendNUIMessage({ action = "setLocales", data = locales })
|
|
243
|
+
*
|
|
244
|
+
* onNuiMessage<Events>((action, data) => {
|
|
245
|
+
* switch (action) {
|
|
246
|
+
* case "setLocales":
|
|
247
|
+
* registerLocales(data);
|
|
248
|
+
* break;
|
|
249
|
+
* }
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
declare function registerLocales(locales: LocaleRecord): void;
|
|
254
|
+
/**
|
|
255
|
+
* Merges new entries into the current global locale map without replacing it.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* registerLocales({ ui: { title: "Dashboard" } });
|
|
260
|
+
* extendLocales({ ui: { subtitle: "Overview" } });
|
|
261
|
+
*
|
|
262
|
+
* _U("ui.title", ""); // → "Dashboard"
|
|
263
|
+
* _U("ui.subtitle", ""); // → "Overview"
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
declare function extendLocales(...records: LocaleRecord[]): void;
|
|
267
|
+
/**
|
|
268
|
+
* Global translator — reads from the locale map set by `registerLocales` / `extendLocales`.
|
|
269
|
+
*
|
|
270
|
+
* @param key Dot-notated key, e.g. `"ui.greeting"` or flat `"title"`
|
|
271
|
+
* @param fallback Returned as-is when the key doesn't exist
|
|
272
|
+
* @param args Values for `%s`, `%d`, `%f` placeholders
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { registerLocales, _U } from "nuix";
|
|
277
|
+
*
|
|
278
|
+
* // After Lua sends locales:
|
|
279
|
+
* // { ui: { greeting: "Hello %s!", level: "Level %d" } }
|
|
280
|
+
*
|
|
281
|
+
* _U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
282
|
+
* _U("ui.level", "Lv.", 42); // → "Level 42"
|
|
283
|
+
* _U("missing.key", "Fallback"); // → "Fallback"
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
declare function _U(key: string, fallback: string, ...args: FormatArg[]): string;
|
|
231
287
|
/**
|
|
232
288
|
* Deep-merges multiple locale records into one.
|
|
233
289
|
* Later records override earlier ones on key conflicts.
|
|
@@ -245,4 +301,4 @@ declare function createTranslator(options: TranslatorOptions): TranslatorFn;
|
|
|
245
301
|
*/
|
|
246
302
|
declare function mergeLocales(...records: LocaleRecord[]): LocaleRecord;
|
|
247
303
|
|
|
248
|
-
export { type FetchNuiFactoryOptions, type FetchNuiOptions, type FormatArg, type LocaleRecord, type NuiEventMap, type NuiMessageHandler, type NuiMessagePayload, type TranslatorFn, type TranslatorOptions, type UnsubscribeFn, createFetchNui, createTranslator, luaFormat, mergeLocales, onNuiMessage };
|
|
304
|
+
export { type FetchNuiFactoryOptions, type FetchNuiOptions, type FormatArg, type LocaleRecord, type NuiEventMap, type NuiMessageHandler, type NuiMessagePayload, type TranslatorFn, type TranslatorOptions, type UnsubscribeFn, _U, createFetchNui, createTranslator, extendLocales, luaFormat, mergeLocales, onNuiMessage, registerLocales };
|
package/dist/index.d.ts
CHANGED
|
@@ -87,16 +87,16 @@ interface TranslatorOptions {
|
|
|
87
87
|
locales: LocaleRecord;
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Translator function
|
|
90
|
+
* Translator function signature used by both `createTranslator` and the global `_U`.
|
|
91
91
|
*
|
|
92
|
-
* @param key
|
|
93
|
-
* @param fallback
|
|
94
|
-
* @param args
|
|
92
|
+
* @param key Dot-notated key like `"ui.greeting"` or flat like `"title"`
|
|
93
|
+
* @param fallback Returned when the key doesn't exist in the locale map
|
|
94
|
+
* @param args Format arguments for `%s`, `%d`, `%f` placeholders
|
|
95
95
|
*
|
|
96
96
|
* @example
|
|
97
97
|
* ```ts
|
|
98
|
-
* _U("
|
|
99
|
-
* _U("
|
|
98
|
+
* _U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
99
|
+
* _U("missing.key", "Fallback"); // → "Fallback"
|
|
100
100
|
* ```
|
|
101
101
|
*/
|
|
102
102
|
type TranslatorFn = (key: string, fallback: string, ...args: FormatArg[]) => string;
|
|
@@ -148,26 +148,30 @@ type NuiMessageHandler<TData = unknown> = (data: TData) => void;
|
|
|
148
148
|
* end)
|
|
149
149
|
* ```
|
|
150
150
|
*/
|
|
151
|
-
declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>): <K extends keyof TMap & string>(event: K, data?: TMap[K]["request"], options?: FetchNuiOptions) => Promise<TMap[K]["response"]>;
|
|
151
|
+
declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>): <K extends keyof TMap & string>(event: K, ...args: TMap[K]["request"] extends void ? [data?: TMap[K]["request"], options?: FetchNuiOptions] : [data: TMap[K]["request"], options?: FetchNuiOptions]) => Promise<TMap[K]["response"]>;
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Listens for NUI messages from Lua (`SendNUIMessage`)
|
|
155
|
-
* Only messages matching the given `action` trigger the handler.
|
|
156
|
-
* Returns a cleanup function to stop listening.
|
|
154
|
+
* Listens for NUI messages from Lua (`SendNUIMessage`).
|
|
157
155
|
*
|
|
158
|
-
*
|
|
156
|
+
* **Per-action** — filters by action name, `data` is fully typed:
|
|
159
157
|
* ```ts
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* hideMenu: { request: void; response: void };
|
|
163
|
-
* }
|
|
164
|
-
*
|
|
165
|
-
* const unsub = onNuiMessage<MyMessages, "showMenu">("showMenu", (data) => {
|
|
166
|
-
* console.log(data.items); // typed as string[]
|
|
158
|
+
* onNuiMessage<Events, "showMenu">("showMenu", (data) => {
|
|
159
|
+
* console.log(data.items); // ✅ typed as string[]
|
|
167
160
|
* });
|
|
161
|
+
* ```
|
|
168
162
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
163
|
+
* **Switch-case** — single listener for all actions:
|
|
164
|
+
* ```ts
|
|
165
|
+
* onNuiMessage<Events>((action, data) => {
|
|
166
|
+
* switch (action) {
|
|
167
|
+
* case "setLocales":
|
|
168
|
+
* registerLocales(data);
|
|
169
|
+
* break;
|
|
170
|
+
* case "showMenu":
|
|
171
|
+
* openMenu(data.items);
|
|
172
|
+
* break;
|
|
173
|
+
* }
|
|
174
|
+
* });
|
|
171
175
|
* ```
|
|
172
176
|
*
|
|
173
177
|
* Lua side:
|
|
@@ -176,6 +180,7 @@ declare function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: Fetch
|
|
|
176
180
|
* ```
|
|
177
181
|
*/
|
|
178
182
|
declare function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(action: K, handler: NuiMessageHandler<TMap[K]["request"]>): UnsubscribeFn;
|
|
183
|
+
declare function onNuiMessage<TMap extends NuiEventMap>(handler: (action: keyof TMap & string, data: any) => void): UnsubscribeFn;
|
|
179
184
|
|
|
180
185
|
/**
|
|
181
186
|
* Formats a string using Lua-style placeholders.
|
|
@@ -228,6 +233,57 @@ declare function luaFormat(template: string, ...args: FormatArg[]): string;
|
|
|
228
233
|
* ```
|
|
229
234
|
*/
|
|
230
235
|
declare function createTranslator(options: TranslatorOptions): TranslatorFn;
|
|
236
|
+
/**
|
|
237
|
+
* Sets the global locale map. Call this when Lua sends locale data to the NUI.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* // Lua side:
|
|
242
|
+
* // SendNUIMessage({ action = "setLocales", data = locales })
|
|
243
|
+
*
|
|
244
|
+
* onNuiMessage<Events>((action, data) => {
|
|
245
|
+
* switch (action) {
|
|
246
|
+
* case "setLocales":
|
|
247
|
+
* registerLocales(data);
|
|
248
|
+
* break;
|
|
249
|
+
* }
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
declare function registerLocales(locales: LocaleRecord): void;
|
|
254
|
+
/**
|
|
255
|
+
* Merges new entries into the current global locale map without replacing it.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* registerLocales({ ui: { title: "Dashboard" } });
|
|
260
|
+
* extendLocales({ ui: { subtitle: "Overview" } });
|
|
261
|
+
*
|
|
262
|
+
* _U("ui.title", ""); // → "Dashboard"
|
|
263
|
+
* _U("ui.subtitle", ""); // → "Overview"
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
declare function extendLocales(...records: LocaleRecord[]): void;
|
|
267
|
+
/**
|
|
268
|
+
* Global translator — reads from the locale map set by `registerLocales` / `extendLocales`.
|
|
269
|
+
*
|
|
270
|
+
* @param key Dot-notated key, e.g. `"ui.greeting"` or flat `"title"`
|
|
271
|
+
* @param fallback Returned as-is when the key doesn't exist
|
|
272
|
+
* @param args Values for `%s`, `%d`, `%f` placeholders
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { registerLocales, _U } from "nuix";
|
|
277
|
+
*
|
|
278
|
+
* // After Lua sends locales:
|
|
279
|
+
* // { ui: { greeting: "Hello %s!", level: "Level %d" } }
|
|
280
|
+
*
|
|
281
|
+
* _U("ui.greeting", "Hi", "Laot"); // → "Hello Laot!"
|
|
282
|
+
* _U("ui.level", "Lv.", 42); // → "Level 42"
|
|
283
|
+
* _U("missing.key", "Fallback"); // → "Fallback"
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
declare function _U(key: string, fallback: string, ...args: FormatArg[]): string;
|
|
231
287
|
/**
|
|
232
288
|
* Deep-merges multiple locale records into one.
|
|
233
289
|
* Later records override earlier ones on key conflicts.
|
|
@@ -245,4 +301,4 @@ declare function createTranslator(options: TranslatorOptions): TranslatorFn;
|
|
|
245
301
|
*/
|
|
246
302
|
declare function mergeLocales(...records: LocaleRecord[]): LocaleRecord;
|
|
247
303
|
|
|
248
|
-
export { type FetchNuiFactoryOptions, type FetchNuiOptions, type FormatArg, type LocaleRecord, type NuiEventMap, type NuiMessageHandler, type NuiMessagePayload, type TranslatorFn, type TranslatorOptions, type UnsubscribeFn, createFetchNui, createTranslator, luaFormat, mergeLocales, onNuiMessage };
|
|
304
|
+
export { type FetchNuiFactoryOptions, type FetchNuiOptions, type FormatArg, type LocaleRecord, type NuiEventMap, type NuiMessageHandler, type NuiMessagePayload, type TranslatorFn, type TranslatorOptions, type UnsubscribeFn, _U, createFetchNui, createTranslator, extendLocales, luaFormat, mergeLocales, onNuiMessage, registerLocales };
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,8 @@ function getResourceName() {
|
|
|
8
8
|
function createFetchNui(factoryOptions) {
|
|
9
9
|
const debug = factoryOptions?.debug ?? false;
|
|
10
10
|
const mockData = factoryOptions?.mockData;
|
|
11
|
-
return async function fetchNui(event,
|
|
11
|
+
return async function fetchNui(event, ...args) {
|
|
12
|
+
const [data, options] = args;
|
|
12
13
|
if (debug) {
|
|
13
14
|
console.log(`[NUIX] \u2192 ${event}`, data ?? {});
|
|
14
15
|
}
|
|
@@ -56,12 +57,17 @@ function createFetchNui(factoryOptions) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// src/listener.ts
|
|
59
|
-
function onNuiMessage(
|
|
60
|
+
function onNuiMessage(actionOrHandler, handler) {
|
|
60
61
|
const listener = (event) => {
|
|
61
62
|
const payload = event.data;
|
|
62
63
|
if (!payload || typeof payload !== "object") return;
|
|
63
|
-
if (payload.action
|
|
64
|
-
|
|
64
|
+
if (!payload.action) return;
|
|
65
|
+
if (typeof actionOrHandler === "string") {
|
|
66
|
+
if (payload.action !== actionOrHandler) return;
|
|
67
|
+
handler(payload.data);
|
|
68
|
+
} else {
|
|
69
|
+
actionOrHandler(payload.action, payload.data);
|
|
70
|
+
}
|
|
65
71
|
};
|
|
66
72
|
window.addEventListener("message", listener);
|
|
67
73
|
return () => {
|
|
@@ -113,6 +119,18 @@ function createTranslator(options) {
|
|
|
113
119
|
return args.length > 0 ? luaFormat(template, ...args) : template;
|
|
114
120
|
};
|
|
115
121
|
}
|
|
122
|
+
var _locales = {};
|
|
123
|
+
function registerLocales(locales) {
|
|
124
|
+
_locales = locales;
|
|
125
|
+
}
|
|
126
|
+
function extendLocales(...records) {
|
|
127
|
+
_locales = mergeLocales(_locales, ...records);
|
|
128
|
+
}
|
|
129
|
+
function _U(key, fallback, ...args) {
|
|
130
|
+
const template = resolveKey(_locales, key);
|
|
131
|
+
if (template === void 0) return fallback;
|
|
132
|
+
return args.length > 0 ? luaFormat(template, ...args) : template;
|
|
133
|
+
}
|
|
116
134
|
function mergeLocales(...records) {
|
|
117
135
|
const result = {};
|
|
118
136
|
for (const record of records) {
|
|
@@ -128,10 +146,13 @@ function mergeLocales(...records) {
|
|
|
128
146
|
return result;
|
|
129
147
|
}
|
|
130
148
|
export {
|
|
149
|
+
_U,
|
|
131
150
|
createFetchNui,
|
|
132
151
|
createTranslator,
|
|
152
|
+
extendLocales,
|
|
133
153
|
luaFormat,
|
|
134
154
|
mergeLocales,
|
|
135
|
-
onNuiMessage
|
|
155
|
+
onNuiMessage,
|
|
156
|
+
registerLocales
|
|
136
157
|
};
|
|
137
158
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/listener.ts","../src/utils.ts"],"sourcesContent":["import type { NuiEventMap, FetchNuiOptions, FetchNuiFactoryOptions } from \"./types\";\r\n\r\n// ─── Resource Name ───\r\n\r\n/**\r\n * Grabs the resource name from FiveM's injected global.\r\n * Falls back to `\"nui-frame-app\"` when running outside the game (local dev, tests).\r\n */\r\nfunction getResourceName(): string {\r\n\tif (typeof window !== \"undefined\" && window.GetParentResourceName) {\r\n\t\treturn window.GetParentResourceName();\r\n\t}\r\n\treturn \"nui-frame-app\";\r\n}\r\n\r\n// ─── FetchNui Factory ───\r\n\r\n/**\r\n * Creates a fully typed `fetchNui` function tied to your event map.\r\n *\r\n * The returned function POSTs JSON to `https://<resourceName>/<event>`,\r\n * which maps 1:1 to `RegisterNUICallback` on the Lua side.\r\n *\r\n * Supports optional debug logging, mock data for local dev, and per-call timeout.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyEvents extends NuiEventMap {\r\n * getPlayer: { request: { id: number }; response: { name: string } };\r\n * notify: { request: { msg: string }; response: void };\r\n * }\r\n *\r\n * // Production usage\r\n * const fetchNui = createFetchNui<MyEvents>();\r\n * const player = await fetchNui(\"getPlayer\", { id: 1 });\r\n * // player.name is typed as string\r\n *\r\n * // Local dev with mocks + debug\r\n * const fetchNui = createFetchNui<MyEvents>({\r\n * debug: true,\r\n * mockData: {\r\n * getPlayer: { name: \"DevPlayer\" },\r\n * notify: (req) => { console.log(\"Mock:\", req.msg); },\r\n * },\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * RegisterNUICallback(\"getPlayer\", function(data, cb)\r\n * local player = GetPlayerData(data.id)\r\n * cb({ name = player.name })\r\n * end)\r\n * ```\r\n */\r\nexport function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>) {\r\n\tconst debug = factoryOptions?.debug ?? false;\r\n\tconst mockData = factoryOptions?.mockData;\r\n\r\n\treturn async function fetchNui<K extends keyof TMap & string>(\r\n\t\tevent: K,\r\n\t\tdata?: TMap[K][\"request\"],\r\n\t\toptions?: FetchNuiOptions,\r\n\t): Promise<TMap[K][\"response\"]> {\r\n\t\tif (debug) {\r\n\t\t\tconsole.log(`[NUIX] → ${event}`, data ?? {});\r\n\t\t}\r\n\r\n\t\t// ─── Mock Mode ───\r\n\r\n\t\tif (mockData && event in mockData) {\r\n\t\t\tconst mock = mockData[event];\r\n\r\n\t\t\tif (mock === undefined) {\r\n\t\t\t\tthrow new Error(`[NUIX] Mock data for \"${event}\" is undefined`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result =\r\n\t\t\t\ttypeof mock === \"function\"\r\n\t\t\t\t\t? (mock as (req: TMap[K][\"request\"]) => TMap[K][\"response\"])(data as TMap[K][\"request\"])\r\n\t\t\t\t\t: mock;\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event} (mock)`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result as TMap[K][\"response\"];\r\n\t\t}\r\n\r\n\t\t// ─── Real Fetch ───\r\n\r\n\t\tconst url = `https://${getResourceName()}/${event}`;\r\n\r\n\t\tconst controller = options?.timeout ? new AbortController() : undefined;\r\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\r\n\r\n\t\tif (controller && options?.timeout) {\r\n\t\t\ttimeoutId = setTimeout(() => controller.abort(), options.timeout);\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst response = await fetch(url, {\r\n\t\t\t\tmethod: \"POST\",\r\n\t\t\t\theaders: { \"Content-Type\": \"application/json; charset=UTF-8\" },\r\n\t\t\t\tbody: JSON.stringify(data ?? {}),\r\n\t\t\t\tsignal: controller?.signal,\r\n\t\t\t});\r\n\r\n\t\t\tif (!response.ok) {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") failed with HTTP ${response.status}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result = (await response.json()) as TMap[K][\"response\"];\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event}`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") timed out after ${options?.timeout}ms`);\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t} finally {\r\n\t\t\tif (timeoutId !== undefined) clearTimeout(timeoutId);\r\n\t\t}\r\n\t};\r\n}\r\n","import type { NuiEventMap, NuiMessagePayload, NuiMessageHandler, UnsubscribeFn } from \"./types\";\r\n\r\n// ─── NUI Message Listener ───\r\n\r\n/**\r\n * Listens for NUI messages from Lua (`SendNUIMessage`), filtered by action name.\r\n * Only messages matching the given `action` trigger the handler.\r\n * Returns a cleanup function to stop listening.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyMessages extends NuiEventMap {\r\n * showMenu: { request: { items: string[] }; response: void };\r\n * hideMenu: { request: void; response: void };\r\n * }\r\n *\r\n * const unsub = onNuiMessage<MyMessages, \"showMenu\">(\"showMenu\", (data) => {\r\n * console.log(data.items); // typed as string[]\r\n * });\r\n *\r\n * // Stop listening when done\r\n * unsub();\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * SendNUIMessage({ action = \"showMenu\", data = { items = {\"Pistol\", \"Rifle\"} } })\r\n * ```\r\n */\r\nexport function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(\r\n\taction: K,\r\n\thandler: NuiMessageHandler<TMap[K][\"request\"]>,\r\n): UnsubscribeFn {\r\n\tconst listener = (event: MessageEvent<NuiMessagePayload<TMap[K][\"request\"]>>) => {\r\n\t\tconst payload = event.data;\r\n\r\n\t\tif (!payload || typeof payload !== \"object\") return;\r\n\t\tif (payload.action !== action) return;\r\n\r\n\t\thandler(payload.data);\r\n\t};\r\n\r\n\twindow.addEventListener(\"message\", listener);\r\n\r\n\treturn () => {\r\n\t\twindow.removeEventListener(\"message\", listener);\r\n\t};\r\n}\r\n","import type { FormatArg, LocaleRecord, TranslatorFn, TranslatorOptions } from \"./types\";\r\n\r\n// ─── Lua-Style Formatter ───\r\n\r\n/**\r\n * Formats a string using Lua-style placeholders.\r\n *\r\n * Specifiers:\r\n * - `%s` — string (null/undefined becomes empty string)\r\n * - `%d` — integer (floors the value, NaN becomes 0)\r\n * - `%f` — float (NaN becomes 0)\r\n * - `%%` — literal percent sign\r\n *\r\n * @example\r\n * ```ts\r\n * luaFormat(\"Hello %s, you are level %d\", \"Laot\", 42);\r\n * // → \"Hello Laot, you are level 42\"\r\n *\r\n * luaFormat(\"Accuracy: %f%%\", 99.5);\r\n * // → \"Accuracy: 99.5%\"\r\n *\r\n * luaFormat(\"Safe: %s %d\", undefined, NaN);\r\n * // → \"Safe: 0\"\r\n * ```\r\n */\r\nexport function luaFormat(template: string, ...args: FormatArg[]): string {\r\n\tlet argIndex = 0;\r\n\r\n\treturn template.replace(/%([sdfi%])/g, (match, specifier: string) => {\r\n\t\tif (specifier === \"%\") return \"%\";\r\n\r\n\t\tconst raw = args[argIndex++];\r\n\r\n\t\tswitch (specifier) {\r\n\t\t\tcase \"s\":\r\n\t\t\t\treturn String(raw ?? \"\");\r\n\r\n\t\t\tcase \"d\":\r\n\t\t\tcase \"i\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : Math.floor(num));\r\n\t\t\t}\r\n\r\n\t\t\tcase \"f\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : num);\r\n\t\t\t}\r\n\r\n\t\t\tdefault:\r\n\t\t\t\treturn match;\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// ─── Dot-Notation Resolver ───\r\n\r\n/**\r\n * Walks a nested locale object by splitting the key on `.`\r\n * Returns the string value at the end of the path, or `undefined` if any segment is missing.\r\n *\r\n * `\"client.greeting\"` → looks up `locales.client.greeting`\r\n * `\"flat_key\"` → looks up `locales.flat_key` directly\r\n */\r\nfunction resolveKey(locales: LocaleRecord, key: string): string | undefined {\r\n\tconst segments = key.split(\".\");\r\n\tlet current: LocaleRecord | string = locales;\r\n\r\n\tfor (const segment of segments) {\r\n\t\tif (typeof current !== \"object\" || current === null) return undefined;\r\n\t\tconst next: string | LocaleRecord | undefined = current[segment];\r\n\t\tif (next === undefined) return undefined;\r\n\t\tcurrent = next;\r\n\t}\r\n\r\n\treturn typeof current === \"string\" ? current : undefined;\r\n}\r\n\r\n// ─── Translator Factory ───\r\n\r\n/**\r\n * Creates a `_U(key, fallback, ...args)` translator bound to a locale record.\r\n *\r\n * The key supports dot-notation to traverse nested locale objects.\r\n * If the key isn't found, the fallback string is returned as-is.\r\n * Any extra args are passed through `luaFormat` for placeholder substitution.\r\n *\r\n * @example\r\n * ```ts\r\n * const _U = createTranslator({\r\n * locales: {\r\n * client: {\r\n * greeting: \"Hello %s!\",\r\n * level: \"Level %d\",\r\n * },\r\n * server: {\r\n * error: \"Error: %s\",\r\n * },\r\n * flat_key: \"Plain message\",\r\n * },\r\n * });\r\n *\r\n * _U(\"client.greeting\", \"MISSING\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"client.level\", \"MISSING\", 42); // → \"Level 42\"\r\n * _U(\"flat_key\", \"MISSING\"); // → \"Plain message\"\r\n * _U(\"no.key\", \"Not found\"); // → \"Not found\"\r\n * ```\r\n */\r\nexport function createTranslator(options: TranslatorOptions): TranslatorFn {\r\n\tconst { locales } = options;\r\n\r\n\treturn (key: string, fallback: string, ...args: FormatArg[]): string => {\r\n\t\tconst template = resolveKey(locales, key);\r\n\r\n\t\tif (template === undefined) {\r\n\t\t\treturn fallback;\r\n\t\t}\r\n\r\n\t\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n\t};\r\n}\r\n\r\n// ─── Deep Merge ───\r\n\r\n/**\r\n * Deep-merges multiple locale records into one.\r\n * Later records override earlier ones on key conflicts.\r\n * Nested objects are merged recursively, not replaced entirely.\r\n *\r\n * @example\r\n * ```ts\r\n * const base = { client: { greeting: \"Hello %s!\", level: \"Level %d\" } };\r\n * const overrides = { client: { greeting: \"Hey %s!\" } };\r\n *\r\n * const merged = mergeLocales(base, overrides);\r\n * // merged.client.greeting → \"Hey %s!\"\r\n * // merged.client.level → \"Level %d\" (preserved from base)\r\n * ```\r\n */\r\nexport function mergeLocales(...records: LocaleRecord[]): LocaleRecord {\r\n\tconst result: LocaleRecord = {};\r\n\r\n\tfor (const record of records) {\r\n\t\tfor (const [key, value] of Object.entries(record)) {\r\n\t\t\tconst existing = result[key];\r\n\r\n\t\t\tif (typeof value === \"object\" && typeof existing === \"object\" && existing !== undefined) {\r\n\t\t\t\tresult[key] = mergeLocales(existing, value);\r\n\t\t\t} else {\r\n\t\t\t\tresult[key] = value;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n"],"mappings":";AAQA,SAAS,kBAA0B;AAClC,MAAI,OAAO,WAAW,eAAe,OAAO,uBAAuB;AAClE,WAAO,OAAO,sBAAsB;AAAA,EACrC;AACA,SAAO;AACR;AA0CO,SAAS,eAAyC,gBAA+C;AACvG,QAAM,QAAQ,gBAAgB,SAAS;AACvC,QAAM,WAAW,gBAAgB;AAEjC,SAAO,eAAe,SACrB,OACA,MACA,SAC+B;AAC/B,QAAI,OAAO;AACV,cAAQ,IAAI,iBAAY,KAAK,IAAI,QAAQ,CAAC,CAAC;AAAA,IAC5C;AAIA,QAAI,YAAY,SAAS,UAAU;AAClC,YAAM,OAAO,SAAS,KAAK;AAE3B,UAAI,SAAS,QAAW;AACvB,cAAM,IAAI,MAAM,yBAAyB,KAAK,gBAAgB;AAAA,MAC/D;AAEA,YAAM,SACL,OAAO,SAAS,aACZ,KAA0D,IAA0B,IACrF;AAEJ,UAAI,OAAO;AACV,gBAAQ,IAAI,iBAAY,KAAK,WAAW,MAAM;AAAA,MAC/C;AAEA,aAAO;AAAA,IACR;AAIA,UAAM,MAAM,WAAW,gBAAgB,CAAC,IAAI,KAAK;AAEjD,UAAM,aAAa,SAAS,UAAU,IAAI,gBAAgB,IAAI;AAC9D,QAAI;AAEJ,QAAI,cAAc,SAAS,SAAS;AACnC,kBAAY,WAAW,MAAM,WAAW,MAAM,GAAG,QAAQ,OAAO;AAAA,IACjE;AAEA,QAAI;AACH,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QACjC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,kCAAkC;AAAA,QAC7D,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC;AAAA,QAC/B,QAAQ,YAAY;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACjB,cAAM,IAAI,MAAM,oBAAoB,KAAK,uBAAuB,SAAS,MAAM,EAAE;AAAA,MAClF;AAEA,YAAM,SAAU,MAAM,SAAS,KAAK;AAEpC,UAAI,OAAO;AACV,gBAAQ,IAAI,iBAAY,KAAK,IAAI,MAAM;AAAA,MACxC;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AACf,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AACjE,cAAM,IAAI,MAAM,oBAAoB,KAAK,sBAAsB,SAAS,OAAO,IAAI;AAAA,MACpF;AACA,YAAM;AAAA,IACP,UAAE;AACD,UAAI,cAAc,OAAW,cAAa,SAAS;AAAA,IACpD;AAAA,EACD;AACD;;;ACnGO,SAAS,aACf,QACA,SACgB;AAChB,QAAM,WAAW,CAAC,UAA+D;AAChF,UAAM,UAAU,MAAM;AAEtB,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU;AAC7C,QAAI,QAAQ,WAAW,OAAQ;AAE/B,YAAQ,QAAQ,IAAI;AAAA,EACrB;AAEA,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,SAAO,MAAM;AACZ,WAAO,oBAAoB,WAAW,QAAQ;AAAA,EAC/C;AACD;;;ACtBO,SAAS,UAAU,aAAqB,MAA2B;AACzE,MAAI,WAAW;AAEf,SAAO,SAAS,QAAQ,eAAe,CAAC,OAAO,cAAsB;AACpE,QAAI,cAAc,IAAK,QAAO;AAE9B,UAAM,MAAM,KAAK,UAAU;AAE3B,YAAQ,WAAW;AAAA,MAClB,KAAK;AACJ,eAAO,OAAO,OAAO,EAAE;AAAA,MAExB,KAAK;AAAA,MACL,KAAK,KAAK;AACT,cAAM,MAAM,OAAO,GAAG;AACtB,eAAO,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC;AAAA,MACtD;AAAA,MAEA,KAAK,KAAK;AACT,cAAM,MAAM,OAAO,GAAG;AACtB,eAAO,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,GAAG;AAAA,MAC1C;AAAA,MAEA;AACC,eAAO;AAAA,IACT;AAAA,EACD,CAAC;AACF;AAWA,SAAS,WAAW,SAAuB,KAAiC;AAC3E,QAAM,WAAW,IAAI,MAAM,GAAG;AAC9B,MAAI,UAAiC;AAErC,aAAW,WAAW,UAAU;AAC/B,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;AAC5D,UAAM,OAA0C,QAAQ,OAAO;AAC/D,QAAI,SAAS,OAAW,QAAO;AAC/B,cAAU;AAAA,EACX;AAEA,SAAO,OAAO,YAAY,WAAW,UAAU;AAChD;AAgCO,SAAS,iBAAiB,SAA0C;AAC1E,QAAM,EAAE,QAAQ,IAAI;AAEpB,SAAO,CAAC,KAAa,aAAqB,SAA8B;AACvE,UAAM,WAAW,WAAW,SAAS,GAAG;AAExC,QAAI,aAAa,QAAW;AAC3B,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,SAAS,IAAI,UAAU,UAAU,GAAG,IAAI,IAAI;AAAA,EACzD;AACD;AAmBO,SAAS,gBAAgB,SAAuC;AACtE,QAAM,SAAuB,CAAC;AAE9B,aAAW,UAAU,SAAS;AAC7B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,YAAM,WAAW,OAAO,GAAG;AAE3B,UAAI,OAAO,UAAU,YAAY,OAAO,aAAa,YAAY,aAAa,QAAW;AACxF,eAAO,GAAG,IAAI,aAAa,UAAU,KAAK;AAAA,MAC3C,OAAO;AACN,eAAO,GAAG,IAAI;AAAA,MACf;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/listener.ts","../src/utils.ts"],"sourcesContent":["import type { NuiEventMap, FetchNuiOptions, FetchNuiFactoryOptions } from \"./types\";\r\n\r\n// ─── Resource Name ───\r\n\r\n/**\r\n * Grabs the resource name from FiveM's injected global.\r\n * Falls back to `\"nui-frame-app\"` when running outside the game (local dev, tests).\r\n */\r\nfunction getResourceName(): string {\r\n\tif (typeof window !== \"undefined\" && window.GetParentResourceName) {\r\n\t\treturn window.GetParentResourceName();\r\n\t}\r\n\treturn \"nui-frame-app\";\r\n}\r\n\r\n// ─── FetchNui Factory ───\r\n\r\n/**\r\n * Creates a fully typed `fetchNui` function tied to your event map.\r\n *\r\n * The returned function POSTs JSON to `https://<resourceName>/<event>`,\r\n * which maps 1:1 to `RegisterNUICallback` on the Lua side.\r\n *\r\n * Supports optional debug logging, mock data for local dev, and per-call timeout.\r\n *\r\n * @example\r\n * ```ts\r\n * interface MyEvents extends NuiEventMap {\r\n * getPlayer: { request: { id: number }; response: { name: string } };\r\n * notify: { request: { msg: string }; response: void };\r\n * }\r\n *\r\n * // Production usage\r\n * const fetchNui = createFetchNui<MyEvents>();\r\n * const player = await fetchNui(\"getPlayer\", { id: 1 });\r\n * // player.name is typed as string\r\n *\r\n * // Local dev with mocks + debug\r\n * const fetchNui = createFetchNui<MyEvents>({\r\n * debug: true,\r\n * mockData: {\r\n * getPlayer: { name: \"DevPlayer\" },\r\n * notify: (req) => { console.log(\"Mock:\", req.msg); },\r\n * },\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * RegisterNUICallback(\"getPlayer\", function(data, cb)\r\n * local player = GetPlayerData(data.id)\r\n * cb({ name = player.name })\r\n * end)\r\n * ```\r\n */\r\nexport function createFetchNui<TMap extends NuiEventMap>(factoryOptions?: FetchNuiFactoryOptions<TMap>) {\r\n\tconst debug = factoryOptions?.debug ?? false;\r\n\tconst mockData = factoryOptions?.mockData;\r\n\r\n\treturn async function fetchNui<K extends keyof TMap & string>(\r\n\t\tevent: K,\r\n\t\t...args: TMap[K][\"request\"] extends void\r\n\t\t\t? [data?: TMap[K][\"request\"], options?: FetchNuiOptions]\r\n\t\t\t: [data: TMap[K][\"request\"], options?: FetchNuiOptions]\r\n\t): Promise<TMap[K][\"response\"]> {\r\n\t\tconst [data, options] = args;\r\n\t\tif (debug) {\r\n\t\t\tconsole.log(`[NUIX] → ${event}`, data ?? {});\r\n\t\t}\r\n\r\n\t\t// ─── Mock Mode ───\r\n\r\n\t\tif (mockData && event in mockData) {\r\n\t\t\tconst mock = mockData[event];\r\n\r\n\t\t\tif (mock === undefined) {\r\n\t\t\t\tthrow new Error(`[NUIX] Mock data for \"${event}\" is undefined`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result =\r\n\t\t\t\ttypeof mock === \"function\"\r\n\t\t\t\t\t? (mock as (req: TMap[K][\"request\"]) => TMap[K][\"response\"])(data as TMap[K][\"request\"])\r\n\t\t\t\t\t: mock;\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event} (mock)`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result as TMap[K][\"response\"];\r\n\t\t}\r\n\r\n\t\t// ─── Real Fetch ───\r\n\r\n\t\tconst url = `https://${getResourceName()}/${event}`;\r\n\r\n\t\tconst controller = options?.timeout ? new AbortController() : undefined;\r\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\r\n\r\n\t\tif (controller && options?.timeout) {\r\n\t\t\ttimeoutId = setTimeout(() => controller.abort(), options.timeout);\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst response = await fetch(url, {\r\n\t\t\t\tmethod: \"POST\",\r\n\t\t\t\theaders: { \"Content-Type\": \"application/json; charset=UTF-8\" },\r\n\t\t\t\tbody: JSON.stringify(data ?? {}),\r\n\t\t\t\tsignal: controller?.signal,\r\n\t\t\t});\r\n\r\n\t\t\tif (!response.ok) {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") failed with HTTP ${response.status}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst result = (await response.json()) as TMap[K][\"response\"];\r\n\r\n\t\t\tif (debug) {\r\n\t\t\t\tconsole.log(`[NUIX] ← ${event}`, result);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\r\n\t\t\t\tthrow new Error(`[NUIX] fetchNui(\"${event}\") timed out after ${options?.timeout}ms`);\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t} finally {\r\n\t\t\tif (timeoutId !== undefined) clearTimeout(timeoutId);\r\n\t\t}\r\n\t};\r\n}\r\n","import type { NuiEventMap, NuiMessagePayload, NuiMessageHandler, UnsubscribeFn } from \"./types\";\r\n\r\n// ─── NUI Message Listener ───\r\n\r\n/**\r\n * Listens for NUI messages from Lua (`SendNUIMessage`).\r\n *\r\n * **Per-action** — filters by action name, `data` is fully typed:\r\n * ```ts\r\n * onNuiMessage<Events, \"showMenu\">(\"showMenu\", (data) => {\r\n * console.log(data.items); // ✅ typed as string[]\r\n * });\r\n * ```\r\n *\r\n * **Switch-case** — single listener for all actions:\r\n * ```ts\r\n * onNuiMessage<Events>((action, data) => {\r\n * switch (action) {\r\n * case \"setLocales\":\r\n * registerLocales(data);\r\n * break;\r\n * case \"showMenu\":\r\n * openMenu(data.items);\r\n * break;\r\n * }\r\n * });\r\n * ```\r\n *\r\n * Lua side:\r\n * ```lua\r\n * SendNUIMessage({ action = \"showMenu\", data = { items = {\"Pistol\", \"Rifle\"} } })\r\n * ```\r\n */\r\n\r\n// Per-action overload\r\nexport function onNuiMessage<TMap extends NuiEventMap, K extends keyof TMap & string>(\r\n\taction: K,\r\n\thandler: NuiMessageHandler<TMap[K][\"request\"]>,\r\n): UnsubscribeFn;\r\n\r\n// Switch-case overload\r\nexport function onNuiMessage<TMap extends NuiEventMap>(\r\n\thandler: (action: keyof TMap & string, data: any) => void,\r\n): UnsubscribeFn;\r\n\r\n// Implementation\r\nexport function onNuiMessage(\r\n\tactionOrHandler: string | ((action: string, data: unknown) => void),\r\n\thandler?: (data: unknown) => void,\r\n): UnsubscribeFn {\r\n\tconst listener = (event: MessageEvent<NuiMessagePayload>) => {\r\n\t\tconst payload = event.data;\r\n\r\n\t\tif (!payload || typeof payload !== \"object\") return;\r\n\t\tif (!payload.action) return;\r\n\r\n\t\tif (typeof actionOrHandler === \"string\") {\r\n\t\t\tif (payload.action !== actionOrHandler) return;\r\n\t\t\thandler!(payload.data);\r\n\t\t} else {\r\n\t\t\tactionOrHandler(payload.action, payload.data);\r\n\t\t}\r\n\t};\r\n\r\n\twindow.addEventListener(\"message\", listener);\r\n\r\n\treturn () => {\r\n\t\twindow.removeEventListener(\"message\", listener);\r\n\t};\r\n}\r\n","import type { FormatArg, LocaleRecord, TranslatorFn, TranslatorOptions } from \"./types\";\r\n\r\n// ─── Lua-Style Formatter ───\r\n\r\n/**\r\n * Formats a string using Lua-style placeholders.\r\n *\r\n * Specifiers:\r\n * - `%s` — string (null/undefined becomes empty string)\r\n * - `%d` — integer (floors the value, NaN becomes 0)\r\n * - `%f` — float (NaN becomes 0)\r\n * - `%%` — literal percent sign\r\n *\r\n * @example\r\n * ```ts\r\n * luaFormat(\"Hello %s, you are level %d\", \"Laot\", 42);\r\n * // → \"Hello Laot, you are level 42\"\r\n *\r\n * luaFormat(\"Accuracy: %f%%\", 99.5);\r\n * // → \"Accuracy: 99.5%\"\r\n *\r\n * luaFormat(\"Safe: %s %d\", undefined, NaN);\r\n * // → \"Safe: 0\"\r\n * ```\r\n */\r\nexport function luaFormat(template: string, ...args: FormatArg[]): string {\r\n\tlet argIndex = 0;\r\n\r\n\treturn template.replace(/%([sdfi%])/g, (match, specifier: string) => {\r\n\t\tif (specifier === \"%\") return \"%\";\r\n\r\n\t\tconst raw = args[argIndex++];\r\n\r\n\t\tswitch (specifier) {\r\n\t\t\tcase \"s\":\r\n\t\t\t\treturn String(raw ?? \"\");\r\n\r\n\t\t\tcase \"d\":\r\n\t\t\tcase \"i\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : Math.floor(num));\r\n\t\t\t}\r\n\r\n\t\t\tcase \"f\": {\r\n\t\t\t\tconst num = Number(raw);\r\n\t\t\t\treturn String(Number.isNaN(num) ? 0 : num);\r\n\t\t\t}\r\n\r\n\t\t\tdefault:\r\n\t\t\t\treturn match;\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// ─── Dot-Notation Resolver ───\r\n\r\n/**\r\n * Walks a nested locale object by splitting the key on `.`\r\n * Returns the string value at the end of the path, or `undefined` if any segment is missing.\r\n *\r\n * `\"client.greeting\"` → looks up `locales.client.greeting`\r\n * `\"flat_key\"` → looks up `locales.flat_key` directly\r\n */\r\nfunction resolveKey(locales: LocaleRecord, key: string): string | undefined {\r\n\tconst segments = key.split(\".\");\r\n\tlet current: LocaleRecord | string = locales;\r\n\r\n\tfor (const segment of segments) {\r\n\t\tif (typeof current !== \"object\" || current === null) return undefined;\r\n\t\tconst next: string | LocaleRecord | undefined = current[segment];\r\n\t\tif (next === undefined) return undefined;\r\n\t\tcurrent = next;\r\n\t}\r\n\r\n\treturn typeof current === \"string\" ? current : undefined;\r\n}\r\n\r\n// ─── Translator Factory ───\r\n\r\n/**\r\n * Creates a `_U(key, fallback, ...args)` translator bound to a locale record.\r\n *\r\n * The key supports dot-notation to traverse nested locale objects.\r\n * If the key isn't found, the fallback string is returned as-is.\r\n * Any extra args are passed through `luaFormat` for placeholder substitution.\r\n *\r\n * @example\r\n * ```ts\r\n * const _U = createTranslator({\r\n * locales: {\r\n * client: {\r\n * greeting: \"Hello %s!\",\r\n * level: \"Level %d\",\r\n * },\r\n * server: {\r\n * error: \"Error: %s\",\r\n * },\r\n * flat_key: \"Plain message\",\r\n * },\r\n * });\r\n *\r\n * _U(\"client.greeting\", \"MISSING\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"client.level\", \"MISSING\", 42); // → \"Level 42\"\r\n * _U(\"flat_key\", \"MISSING\"); // → \"Plain message\"\r\n * _U(\"no.key\", \"Not found\"); // → \"Not found\"\r\n * ```\r\n */\r\nexport function createTranslator(options: TranslatorOptions): TranslatorFn {\r\n\tconst { locales } = options;\r\n\r\n\treturn (key: string, fallback: string, ...args: FormatArg[]): string => {\r\n\t\tconst template = resolveKey(locales, key);\r\n\r\n\t\tif (template === undefined) {\r\n\t\t\treturn fallback;\r\n\t\t}\r\n\r\n\t\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n\t};\r\n}\r\n\r\n// ─── Global Locale Registry ───\r\n\r\nlet _locales: LocaleRecord = {};\r\n\r\n/**\r\n * Sets the global locale map. Call this when Lua sends locale data to the NUI.\r\n *\r\n * @example\r\n * ```ts\r\n * // Lua side:\r\n * // SendNUIMessage({ action = \"setLocales\", data = locales })\r\n *\r\n * onNuiMessage<Events>((action, data) => {\r\n * switch (action) {\r\n * case \"setLocales\":\r\n * registerLocales(data);\r\n * break;\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function registerLocales(locales: LocaleRecord): void {\r\n\t_locales = locales;\r\n}\r\n\r\n/**\r\n * Merges new entries into the current global locale map without replacing it.\r\n *\r\n * @example\r\n * ```ts\r\n * registerLocales({ ui: { title: \"Dashboard\" } });\r\n * extendLocales({ ui: { subtitle: \"Overview\" } });\r\n *\r\n * _U(\"ui.title\", \"\"); // → \"Dashboard\"\r\n * _U(\"ui.subtitle\", \"\"); // → \"Overview\"\r\n * ```\r\n */\r\nexport function extendLocales(...records: LocaleRecord[]): void {\r\n\t_locales = mergeLocales(_locales, ...records);\r\n}\r\n\r\n/**\r\n * Global translator — reads from the locale map set by `registerLocales` / `extendLocales`.\r\n *\r\n * @param key Dot-notated key, e.g. `\"ui.greeting\"` or flat `\"title\"`\r\n * @param fallback Returned as-is when the key doesn't exist\r\n * @param args Values for `%s`, `%d`, `%f` placeholders\r\n *\r\n * @example\r\n * ```ts\r\n * import { registerLocales, _U } from \"nuix\";\r\n *\r\n * // After Lua sends locales:\r\n * // { ui: { greeting: \"Hello %s!\", level: \"Level %d\" } }\r\n *\r\n * _U(\"ui.greeting\", \"Hi\", \"Laot\"); // → \"Hello Laot!\"\r\n * _U(\"ui.level\", \"Lv.\", 42); // → \"Level 42\"\r\n * _U(\"missing.key\", \"Fallback\"); // → \"Fallback\"\r\n * ```\r\n */\r\nexport function _U(key: string, fallback: string, ...args: FormatArg[]): string {\r\n\tconst template = resolveKey(_locales, key);\r\n\tif (template === undefined) return fallback;\r\n\treturn args.length > 0 ? luaFormat(template, ...args) : template;\r\n}\r\n\r\n// ─── Deep Merge ───\r\n\r\n/**\r\n * Deep-merges multiple locale records into one.\r\n * Later records override earlier ones on key conflicts.\r\n * Nested objects are merged recursively, not replaced entirely.\r\n *\r\n * @example\r\n * ```ts\r\n * const base = { client: { greeting: \"Hello %s!\", level: \"Level %d\" } };\r\n * const overrides = { client: { greeting: \"Hey %s!\" } };\r\n *\r\n * const merged = mergeLocales(base, overrides);\r\n * // merged.client.greeting → \"Hey %s!\"\r\n * // merged.client.level → \"Level %d\" (preserved from base)\r\n * ```\r\n */\r\nexport function mergeLocales(...records: LocaleRecord[]): LocaleRecord {\r\n\tconst result: LocaleRecord = {};\r\n\r\n\tfor (const record of records) {\r\n\t\tfor (const [key, value] of Object.entries(record)) {\r\n\t\t\tconst existing = result[key];\r\n\r\n\t\t\tif (typeof value === \"object\" && typeof existing === \"object\" && existing !== undefined) {\r\n\t\t\t\tresult[key] = mergeLocales(existing, value);\r\n\t\t\t} else {\r\n\t\t\t\tresult[key] = value;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@laot/nuix",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Modular, type-safe TypeScript library for FiveM NUI projects",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.cjs",
|
|
7
8
|
"module": "./dist/index.js",
|
|
@@ -42,6 +43,9 @@
|
|
|
42
43
|
"url": "https://github.com/laot7490/nuix/issues"
|
|
43
44
|
},
|
|
44
45
|
"license": "MIT",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
45
49
|
"devDependencies": {
|
|
46
50
|
"tsup": "^8.4.0",
|
|
47
51
|
"typescript": "^5.7.3"
|