AI Core
Referencia de la APILib: servidor

Panel de administración

AdminPanel y DataFile: pon los ajustes de tu script en el panel de administración de ai-core, léelos, reacciona a las ediciones y guarda el contenido del panel en data/*.json.

TypeScript
import { AdminPanel, DataFile } from "@aicore-fivem/lib/server";

Cada script ai-* guarda los ajustes que ajusta el dueño del servidor en el panel de administración de ai-core, no en un archivo de configuración. Describes los datos y el formulario en resource/server/schema.ts (PanelSchema: archivos, pestañas, tarjetas, widgets; consulta Crear un script), creas un AdminPanel a partir de él y lees los valores a través de él. El lado del dueño (abrir el panel, editar, permisos) está en Panel de administración.

Sin equivalente en Lua: el panel de administración es solo para TypeScript.

AdminPanel

El contenido del panel de tu recurso. Carga cada data/<file>.json una vez al arrancar, lo mantiene en memoria, comprueba cada edición del panel contra el esquema, guarda el archivo (de forma atómica, una ráfaga de ediciones se escribe una sola vez) y avisa a tu código, a los paneles abiertos y, para los archivos sync: "all", a cada cliente. También escribe data/schemas/<file>.schema.json para que un editor pueda comprobar una edición a mano.

Lado: Servidor · Espera: no

Firma
new AdminPanel(schema: PanelSchema)

Prop

Type

Crea exactamente uno, a nivel de módulo, normalmente en resource/server/content.ts: export const panel = new AdminPanel(schema);. Los archivos de ajustes reciben al cargar las secciones que faltan desde defaults. ai-core muestra el panel un tick después de crearlo.

AdminPanel.get

El contenido actual de un archivo.

Firma
get<T = Record<string, unknown>>(file: string): T

Devuelve el objeto de ajustes (un archivo settings) o { key: record } (un archivo records); {} para un archivo desconocido. Solo lectura: nunca cambies el objeto devuelto, usa commit.

const settings = panel.get<{ prices: { repair: number } }>("settings");

AdminPanel.view

Un archivo de ajustes procesado por tu lector y mantenido al día en cada cambio (se procesa antes de que se ejecuten los handlers de onChange).

Firma
view<T>(file: string, read: (raw: unknown) => T): { readonly value: T }

Devuelve un objeto cuyo value es siempre el último contenido procesado. Lee .value cada vez; no lo copies una sola vez.

import { int, record } from "@aicore-fivem/lib/shared";

const settings = panel.view("settings", (raw) => ({
  repairPrice: int(record(record(raw).prices).repair, 250),
}));
// más tarde: settings.value.repairPrice

AdminPanel.records

Un archivo de registros procesado registro a registro. Un cambio vuelve a leer solo el registro cambiado.

Firma
records<T>(file: string, readOne: (key: string, raw: Record<string, unknown>) => T | null): ReadonlyMap<string, T>

Devuelve un mapa vivo de la clave del registro a tu valor procesado. Si readOne devuelve null, el registro queda fuera.

const shops = panel.records("shops", (key, raw) =>
  typeof raw.label === "string" ? { key, label: raw.label } : null,
);
for (const shop of shops.values()) console.log(shop.label);

AdminPanel.onChange

Ejecuta tu handler después de que un archivo cambió, por una edición del panel o por tu propio commit.

Firma
onChange(file: string, handler: (key: string | undefined, value: unknown) => void): void

Prop

Type

panel.onChange("shops", (key, value) => {
  if (value === null) console.log(`shop ${key} deleted`);
});

AdminPanel.rule

Una comprobación entre campos que se ejecuta antes de guardar una edición. El esquema comprueba valores sueltos; una regla comprueba cómo encajan los valores entre sí.

Firma
rule(file: string, rule: (key: string | undefined, next: Record<string, unknown>) => string | null): void

Devuelve (desde tu regla) null para aceptar, o una clave de idioma para rechazar (se muestra al admin). Una regla por archivo; una segunda llamada la reemplaza.

panel.rule("settings", (_key, next) => {
  const prices = next.prices as { min: number; max: number };
  return prices.min > prices.max ? "ERR_MIN_ABOVE_MAX" : null;
});

AdminPanel.onAction

Se ejecuta cuando un admin pulsa un widget action (un botón: enviar, restablecer, expulsar). No se guarda nada para el botón.

Firma
onAction(id: string, handler: (admin: number, record: Record<string, unknown>, key: string | undefined) => string | null): void

Prop

Type

import { notify } from "@aicore-fivem/lib/server";

panel.onAction("testAlert", (admin) => {
  notify(admin, "Test alert sent", "success");
  return null;
});

AdminPanel.commit

Guarda un cambio que hizo tu propio script (estado del juego que muestra el panel), sin validación. Un archivo memory solo lo guarda en memoria.

Firma
commit(file: string, key: string | undefined, value: unknown): void

Prop

Type

panel.commit("vaults", "bank_1", { ...panel.get<Record<string, object>>("vaults").bank_1, robbedAt: Date.now() });

AdminPanel.commitAfter

commit después de que el cambio actual llegó al panel. Úsalo para un formulario que se restablece solo después de que un admin lo envió.

Firma
commitAfter(file: string, key: string | undefined, value: unknown): void
panel.onAction("sendBroadcast", (admin, form) => {
  if (typeof form.message !== "string" || form.message === "") return "ERR_EMPTY_MESSAGE";
  for (const player of getPlayers()) notify(Number(player), form.message, "info", 10_000);
  panel.commitAfter("broadcast", undefined, { message: "" }); // vaciar el formulario
  return null;
});

Nunca llames a commit ni a commitAfter para el mismo archivo desde dentro de su propio handler de onChange: el commit dispara onChange otra vez, para siempre.

AdminPanel.editor

Firma
editor: number

El id de servidor del admin cuya edición o acción se está aplicando, legible dentro de los handlers de rule, onChange y onAction; 0 para tu propio commit. Solo lo fija la lib.

panel.onChange("settings", () => {
  if (panel.editor) console.log(`settings changed by ${panel.editor}`);
});

Lo usa ai-core

snapshot(), dataSnapshot(), summary() y apply(admin, patch) son lo que ai-core llama mediante los exports que registra el panel (AdminPanelSnapshot, AdminPanelData, AdminPanelSummary, AdminPanelApply). Tu script no los llama.

Ejemplo

resource/server/content.ts
import { AdminPanel, aiLog } from "@aicore-fivem/lib/server";
import { int, record } from "@aicore-fivem/lib/shared";
import { schema } from "./schema";

export const panel = new AdminPanel(schema);

export const settings = panel.view("settings", (raw) => ({
  repairPrice: int(record(record(raw).prices).repair, 250),
}));

panel.onChange("settings", () => {
  aiLog("admin", `repair price is now ${settings.value.repairPrice}`, { source: panel.editor || undefined });
});

Nunca leas data/*.json tú mismo ni escribas en él: el panel es su dueño. Los datos que el mundo necesita en los clientes (lugares, blips, precios que ven los jugadores) son un archivo sync: "all" que se lee en el cliente con AdminWorld; todo lo demás se queda en el servidor.

DataFile

Un archivo data/<name>.json de tu recurso con escrituras seguras. AdminPanel lo usa para cada archivo; úsalo directamente solo para contenido fuera del panel. El servidor es el único que lee y escribe: lee una vez al arrancar y mantiene la verdad en memoria. Una escritura va a un archivo temporal que luego reemplaza al real (un crash nunca deja medio archivo), el archivo anterior se guarda como .bak, y un archivo dañado recurre al .bak al arrancar.

Lado: Servidor · Espera: no

Sin equivalente en Lua.

Firma
new DataFile(resource: string, name: string, log: Logger)

Prop

Type

DataFile.read

Firma
read(): unknown

Devuelve el archivo procesado sin su clave $schema, el .bak cuando el archivo está dañado, o null cuando no existe ninguno de los dos. Procesa el resultado antes de usarlo.

DataFile.write

Firma
write(value: unknown): void

Guarda value; gana la última llamada. Se escribe en disco 250 ms después de la primera llamada de una ráfaga, en orden con las escrituras anteriores. Una escritura fallida se registra en el log.

DataFile.schemaRef

Firma
schemaRef: string | null

Cuando se fija (./schemas/<name>.schema.json), se escribe como primera clave del archivo "$schema", nunca como parte de los datos.

DataFile.hasSchemaRef

Firma
hasSchemaRef(): boolean

Devuelve si el archivo en disco indica su esquema.

Ejemplo

import { DataFile } from "@aicore-fivem/lib/server";
import { createLogger, isRecord } from "@aicore-fivem/lib/shared";

const resource = GetCurrentResourceName();
const file = new DataFile(resource, "routes", createLogger(resource));

const raw = file.read();
const routes: Record<string, unknown> = isRecord(raw) ? raw : {};

function saveRoute(id: string, route: unknown) {
  routes[id] = route;
  file.write(routes);
}

Los datos de los jugadores nunca van a un archivo de datos: pertenecen a la base de datos (Base de datos). Los secretos (webhooks, claves de API) van en data/secrets.json, que git ignora.

En esta página