Перейти к содержимому

Lua API reference (node)

Generated from sdk/api.toml by sdk/tools/apigen.py docs. Do not edit; edit the schema.

This is the API a resource is written against. It lives in sdk/lua/prelude.lua, which the server runs into every resource’s Lua state on top of node.raw – the 1:1 mirror of the C ABI (Raw Lua reference (node.raw.*)), still there as the escape hatch. Signatures read node.ns.name(params) -> result; Player/Vehicle are objects (methods with :); a ? marks an optional parameter or a result that can be nil. A data argument that accepts a table is JSON-encoded for you; a payload documented as a table was JSON-decoded for you.

Guides: Getting started · Events · Concurrency · Wire events and clients · Recipes · Conventions

What every resource uses every day sits directly on node: subscribing to events, sending to clients, timers, and the coroutine helpers. Handlers receive OBJECTS – a Player, a Vehicle – where the raw API has ids, and decoded tables where a payload is documented as JSON.

Raw: node.raw.on

Subscribes fn to an event. The name decides what fn receives: engine events pass a Player or a Vehicle (or nothing for serverTick/serverShutdown); notifications and cancellable requests pass (player, vehicle, payload) with the payload decoded when it is JSON on the wire (the vehicle is left out where none is involved: playerConnectRequest, playerVerifyReported); any other name is a wire event from the client half and passes (player, data) with data as the string the client sent. A cancellable handler returns false[, reason] to deny. The events reference lists every name with its exact arguments. Several handlers per name are fine, across resources too; subscribing the same function twice replaces the first. A deprecated name (playerJoin, onPlayerConnectRequest, canRelay, …; the Formerly line of each event) subscribes to the same event as its canonical name and logs one warning per resource per old name (once per load: the once-table lives in the resource’s state, so a reload warns again) – so the same function under both spellings is one subscription, two different functions are two handlers.

Raw: node.raw.off

Unsubscribes this resource’s handlers for the name – all of them, or only fn – and returns how many were removed. fn is the function that was given to node.on, matched by identity from any context, including coroutines (node.async, pg transaction bodies). A deprecated name is mapped like in node.on, so either spelling unsubscribes what either spelling subscribed.

Raw: node.raw.emitClient

Sends a wire event to one player. target is a Player or an id; data is a table (JSON-encoded for you) or a string. false when the player is not connected. player:send is the same call.

Raw: node.raw.emitAll, emitOthers

Sends a wire event to every connected player – or to everyone except one (a Player or an id), which is the shape of a relay: the excepted player counts as the sender for the relay filter and visibility groups. data is a table or a string.

Raw: node.raw.setTimeout

Runs fn once after ms milliseconds on the worker thread; returns a timer id for node.cancel.

Raw: node.raw.setInterval

Runs fn every ms milliseconds on the worker thread until cancelled; returns a timer id. A slow fn delays the next run; runs do not stack.

Raw: node.raw.clearTimer

Cancels a timer from after or every. Safe for an id that already fired.

Raw: node.raw.setImmediate

Runs fn after the current batch of handlers has finished – the way to act once every other handler of the event you are in has seen it.

Raw: node.raw.async

Runs fn(…) as a cooperative coroutine on the worker and returns its task id. Inside it node.sleep, node.wait, node.await, node.yield and node.http.fetch suspend the coroutine while everything else keeps running.

Raw: node.raw.sleep

Suspends the current coroutine for ms milliseconds. Only inside node.async.

Raw: node.raw.wait

A number sleeps; a function suspends the coroutine until it returns truthy, polled every intervalMs (default 50). Only inside node.async.

Raw: node.raw.yield

Gives the worker back for one turn inside a long loop that must run on it. Fairness, not parallelism. Only inside node.async.

Raw: node.raw.await

Runs a SELF-CONTAINED function (no upvalues, no node, JSON-serialisable args and result) on a background pool thread and suspends the coroutine until it returns; gives back the result, or nil plus an error. Only inside node.async.

Raw: node.raw.job

The callback form of node.await: workFn runs on the pool, doneFn(result, err) on the worker. false when workFn could not be dumped.

Raw: node.raw.log

Prints msg to the server console under this resource’s name. Extra arguments are string.format arguments: node.log("%s joined", player.name). The table also carries the levels: node.log.warn, node.log.error, and the raw forms below.

The [config] table of this resource’s resource.toml, as written (strings, numbers, booleans, arrays, nested tables); an empty table when there is none. Read once at load; node.resources.manifest().config re-reads the file.

The 1:1 mirror of the C ABI: one function per C entry, ids in, tables out (Raw Lua reference (node.raw.*)). Everything above is built on it; reach for it when you need exactly the C behaviour.

Look players up and iterate them. get returns a Player object for any id (cheap: nothing is fetched until a field is read), so it is fine to call in a 60 Hz handler.

The Player with this id – a Player or a number-like table. Cheap: nothing is fetched until a field is read, so it is fine inside a 60 Hz handler. nil for a negative or non-numeric id; an unknown id gives a Player whose isConnected() is false.

Raw: node.raw.getPlayers

Every connected player, as Players. Ascending by id.

Raw: node.raw.getPlayerCount

How many players are connected.

Raw: node.raw.getPlayers

The connected player ids.

The connected player with this display name, case-insensitively; nil when none.

A Player is a table with id and a metatable. Reading any other field fetches the player’s record once and caches it on the object (refresh() drops the cache); methods act by id, so an object outlives the record it was made from – player:isConnected() tells you whether it still refers to someone. Two Players are == when their ids match; tostring(player) is Player#<id> <name>.

The player id – small, assigned at connect and REUSED after a disconnect. Key durable data by name or ip, not by id.

The display name. Still known inside a playerLeft handler (the prelude remembers it), nil for an id that was never seen.

The remote IP as text; what an IP ban keys on. For a verified account, accountId is the better key – it does not change with the player’s network.

The remote TCP port.

Seconds since the connection was accepted.

true once the player finished joining (received the world).

true while the UDP channel is bound; false means positions fall back to TCP.

Seconds since the last keepalive: a freshness metric (0-1 healthy), not a round-trip time.

The role a plugin assigned with setRole (“” when none). Per session.

The visibility group; 0 is the shared world.

The vehicle the player occupies in any seat, or nil on foot. vehicleId is the same as a number (-1 on foot).

“driver”, “passenger” or “none”.

How many vehicles the player drives or spawned.

The global id of the walking avatar, -1 when seated.

true when the join ticket was redeemed with the directory (wire v17): the name is then the account’s username or the guest name the directory minted, and somebody other than the player vouched for it. false on a server with no [Directory], for a join that brought no ticket, and for one admitted through RedeemFailOpen.

true when there is no account behind the name: a Test Drive session, or nothing verified at all. verified and not guest is “a signed-in account”.

The directory’s account id, nil for a guest. Stable across sessions and addresses – the key to persist anything about a player by, and what node.bans.add(accountId) bans.

The directory’s role string for the account (“ADM” for a directory admin; “” for everyone else and for guests). Distinct from role, which is this server’s own per-session role.

What the directory knows the player by: “nodemp:<id>”, “discord:<id>”, “ip:<addr>”. Empty for an unverified player.

Raw: node.raw.isPlayerConnected

true while a player with this id is connected. Ids are reused, so this cannot say it is the SAME player you saw earlier.

Drops the cached record so the next field read fetches it again. Returns the player for chaining.

Raw: node.raw.kickPlayer

Disconnects the player, showing the reason. false for an unknown id.

Raw: node.raw.banPlayer

Bans the player and kicks it with the reason: its account when one was verified (the ban follows the account to any address) and its IP always. Persisted in bans.json.

Raw: node.raw.verifyPlayer

Asks the player’s launcher helper to check the game install again, now, at level "size" (file lengths), "scripts" (plus a hash of the game’s lua/ and ui/), "full" (hash everything – slow, an audit) or "strict" (the archive tables of contents against the server’s reference manifest, the whole game root, the user folder). The answer arrives as the playerVerifyReported event, after the server applied its own verdict (a “differs” or “failed” report kicks the player when [General] VerifyGame is not off). Returns true when the request went out, or nil and a reason: "unknown player"; "unsupported" – that level is not available for this player (its launcher is too old for it, or strict is not on offer because this server has no reference manifest); "pending" – a request to this player is still counted as in flight (the server accepts one per player per 60 s; the check every join runs does not count, so a player:verify from a playerJoined handler goes out at once). Any other level raises Player:verify: unknown level '...' at the caller’s line.

Raw: node.raw.setPlayerStrict

Marks the session strict (on true) or lifts the mark (false or nil; any value other than false/nil counts as on – including 0). The server then refuses Vehicle::Camera frames FROM the player that target a vehicle it does not occupy – they are not relayed, so a strict client cannot follow other people’s cars with its camera; frames from other players to it are unaffected. A refused report still fires playerCameraChanged, with denied = true in player:camera(). Server-side only: tell the client about its strict mode yourself (the session config). Per session, cleared at disconnect. Returns true, or nil and "unknown player".

Raw: node.raw.emitClient

Sends a wire event to this player; data is a table (JSON-encoded) or a string.

Raw: node.raw.sendModule

Sends raw bytes to this player on a u32 module channel.

Raw: node.raw.setPlayerRole

Assigns the per-session role string (“” clears). The client shows it as a tag beside the name.

Raw: node.raw.setPlayerGroup

Puts the player in a visibility group; 0 is the shared world. Move its vehicles too (vehicle:setGroup).

Raw: node.raw.getPlayerVehicles

The vehicles the player drives or spawned, as Vehicles.

Raw: node.raw.getPlayerPosition

The last known position, from the occupied vehicle or the walking avatar; nil before the first snapshot.

Raw: node.raw.getPlayerFocus

The point the relay’s distance filter uses for this player: the camera when a head pose streams, else the driven vehicle.

Raw: node.raw.getPlayerHeadPose

The last camera/head pose (about 10 Hz); nil until one arrived.

Raw: node.raw.getPlayerCamera

Which vehicle the camera is on – its own or the one spectated; vehicle is -1 for none. In a strict session (setStrict) a report the server refused to relay – its target is a vehicle the player does not occupy – carries denied = true; the field is absent otherwise.

player:inputs() -> record{vehicle,controls,controlsAgeSeconds,axes,axesAgeSeconds}?

Заголовок раздела «player:inputs() -> record{vehicle,controls,controlsAgeSeconds,axes,axesAgeSeconds}?»

Raw: node.raw.getPlayerInputs

Everything the player is pressing: the driven vehicle, the five controls and gear from the 60 Hz snapshot, the extra axes, each with its age. nil unless the player drives.

Raw: node.raw.seatPlayer

Seats the player in the vehicle (a Vehicle or a gid) as “driver” (default) or “passenger”, bypassing locks; a previous driver becomes a passenger.

Raw: node.raw.unseatPlayer

Puts the player on foot. false when it already is.

Raw: node.raw.swapPlayers

Exchanges seats – role and vehicle – with another Player (or id), across the whole driver/passenger/on-foot matrix.

Raw: node.raw.resyncPlayer

Re-sends every vehicle’s spawn and cached state to this player; returns how many bundles went out.

Says a system line in this player’s chat (via the chat resource; silent without it). Extra arguments are format arguments.

Look vehicles up and iterate them. Unicycles – the walking avatars – are vehicles to the server; all() leaves them out unless asked, get returns them like any other.

The Vehicle with this global id, cheap like players.get. nil for a negative or non-numeric id; an unknown id gives a Vehicle whose exists() is false.

Raw: node.raw.getVehicles

Every vehicle in the registry as Vehicles with their records already loaded. Walking avatars (unicycles) are left out unless includeUnicycles is true.

Raw: node.raw.getVehicleCount

How many vehicles the registry holds, unicycles included.

Raw: node.raw.spawnVehicle

Spawns an ownerless vehicle from a config (a table or JSON text in the client SpawnReq schema: jbm, vcf, pos, rot, …) and hands its simulation to the best available client. nil when nobody can host it.

Raw: node.raw.deleteAllVehicles

Deletes every vehicle; returns how many.

Raw: node.raw.getVehiclesByTag

Vehicles carrying the tag key – any value, or exactly value.

Raw: node.raw.getVehiclesByTags

Vehicles whose tags match EVERY key = value pair of the table (AND).

node.vehicles.transforms() -> array<record{id,pos,rot,vel,angVel,time,ping,seq,paused,transitioning,teleport,velocityStep,hasControls,ageSeconds}>

Заголовок раздела «node.vehicles.transforms() -> array<record{id,pos,rot,vel,angVel,time,ping,seq,paused,transitioning,teleport,velocityStep,hasControls,ageSeconds}>»

Raw: node.raw.getVehicleTransforms

The positions table: every vehicle’s last snapshot in one array under one lock – the read for a radar polling every tick.

A Vehicle is a table with id and a metatable, lazy like a Player: the registry record is fetched on the first field read and cached until refresh(). Ids that name players come as Players (driver, spawner, authority, passengers) with the raw ids beside them (driverId, …). Everything that streams – transform, controls, electrics – is a method, because it is a fresh read each time.

The global id, unique for the life of the server.

The owner label the spawn carried (the spawner’s display name for a player’s car, the ownerName given to vehicles.spawn).

The Player that spawned it, nil for a server-spawned one. spawnerId is the id (-1).

The Player at the wheel, nil when empty. driverId is the id (-1).

The Player simulating it (not necessarily the spawner). authorityId is the id.

The passengers as Players. passengerIds are the ids.

true for a walking avatar.

How long nobody has driven it; 0 while driven.

Whether the spawning player is still connected.

“unlocked”, “locked” or “driver-only”. lockWhitelist are the player ids always allowed in.

FNV-1a-32 of the cached config – what clients echo so a desynced config is detectable.

The authority-reported damage version. damageBlobCounter and damageBlobSize describe the stored blob (0 = pristine).

The tags as key -> value.

The visibility group; 0 is the shared world.

true while the registry has this id.

Drops the cached record. Returns the vehicle for chaining.

Raw: node.raw.deleteVehicle

Deletes the vehicle for everyone; vehicleDeleted follows.

vehicle:transform() -> record{pos,rot,vel,angVel,time,ping,seq,paused,transitioning,teleport,velocityStep,hasControls,ageSeconds}?

Заголовок раздела «vehicle:transform() -> record{pos,rot,vel,angVel,time,ping,seq,paused,transitioning,teleport,velocityStep,hasControls,ageSeconds}?»

Raw: node.raw.getVehicleTransform

The last kinematic snapshot; nil before the first. A few ticks old by construction.

vehicle:controls() -> record{steering,throttle,brake,clutch,parkingBrake,gear}?

Заголовок раздела «vehicle:controls() -> record{steering,throttle,brake,clutch,parkingBrake,gear}?»

Raw: node.raw.getVehicleControls

The driving inputs from the last snapshot; nil while no driver has sent one.

Raw: node.raw.getVehicleInputs

The driver’s extra (modded) input axes, merged from the delta stream.

Raw: node.raw.getVehicleElectrics

The merged electrics (lights, signals, gauges).

Raw: node.raw.getVehiclePowertrain

The merged powertrain device state.

Raw: node.raw.getVehicleEngine

The merged engine state.

Raw: node.raw.getVehicleControllers

The merged controller calls, “controller|function” -> call.

Raw: node.raw.getVehicleCouplers

The merged coupler/door state.

Raw: node.raw.getVehicleNodes

The last node-position (deformation) packet, as sent.

Raw: node.raw.getVehicleBreakGroups

The break groups reported broken.

Raw: node.raw.getVehiclePaints

The paint layers from the cached config.

Raw: node.raw.getVehicleConfig

The cached spawn/edit config – what a joiner receives.

Raw: node.raw.getVehicleDamage

Damage telemetry with the raw blob (a binary string, nil when pristine).

vehicle:sync() -> record{authority,authorityAgeSeconds,seatEpoch,spawnedAgeSeconds,idleSeconds,configChangedAgeSeconds,hasPos,posAgeSeconds,posSeq}?

Заголовок раздела «vehicle:sync() -> record{authority,authorityAgeSeconds,seatEpoch,spawnedAgeSeconds,idleSeconds,configChangedAgeSeconds,hasPos,posAgeSeconds,posSeq}?»

Raw: node.raw.getVehicleSync

How alive the sync is: the authority and its heartbeat age, seat epoch, ages. The record for ‘why is this car frozen’.

Raw: node.raw.getVehicleOccupants

Who is in it, driver first: { player = Player, role = "driver" | "passenger" }.

Raw: node.raw.getVehicleTag

One tag’s value.

Raw: node.raw.setVehicleTag

Sets a tag (key 1-64 bytes, value 1-256, 32 keys per vehicle; over-limit is rejected). Broadcast to clients; vehicleTagsChanged fires.

Raw: node.raw.removeVehicleTag

Removes a tag; false when it was not there.

Raw: node.raw.setVehicleLock

Locks the vehicle against NEW seat claims: mode “locked” (default), “driver-only” or “unlocked”; whitelist is an array of Players or ids always allowed (up to 32). Nobody seated is evicted; server-driven seating bypasses locks.

Raw: node.raw.setVehicleLock

lock("unlocked").

Raw: node.raw.getVehicleLock

The current lock.

Raw: node.raw.setVehicleCoupler

Opens (default) or closes the named coupler group on every client and caches it.

Raw: node.raw.triggerVehicle

Has the sync authority execute one controller call – a table or JSON {controllerName, functionName, ...}. Never asks vehicleTriggerRequest: the server does not veto itself.

Raw: node.raw.resyncVehicle

Re-sends the spawn and cached state to one Player (or id), or to everyone.

Raw: node.raw.seatPlayer

Seats a Player (or id) in this vehicle; player:seat(vehicle) is the same call.

Raw: node.raw.setVehicleGroup

Puts the vehicle in a visibility group. A packet about a vehicle belongs to the vehicle’s group, not its driver’s.

The server’s own facts and knobs.

Raw: node.raw.getServerName

The server’s name.

Raw: node.raw.setServerName

Changes the reported name.

Raw: node.raw.getMap

The map, as the client-facing level path.

Raw: node.raw.getServerVersion

The server’s version string.

Raw: node.raw.getPort

The port (TCP and UDP).

Raw: node.raw.getMaxPlayers

The player limit in force.

Raw: node.raw.setMaxPlayers

Changes the player limit; enforced on the next connect, nobody is kicked.

Raw: node.raw.getMaxCars

The per-player vehicle limit in force.

Raw: node.raw.setMaxCars

Changes the per-player vehicle limit; enforced on the next spawn.

Raw: node.raw.getServerUptime

Seconds since start (monotonic, fractional).

Raw: node.raw.getTime

Unix time with fractions.

Raw: node.raw.getUnixTime

Unix time in whole seconds.

Raw: node.raw.getMetrics

Live metrics: players, vehicles, uptime, net counters, connection limiter, plugin queue.

Raw: node.raw.getWorldSnapshot

One bulk read of the world: players with position and seat, vehicles with pos/rot/vel.

Raw: node.raw.isNodeGrabEnabled

Whether the experimental node grabber is enabled in server.toml.

IP bans, persisted in bans.json. Player:ban() is the one that also kicks.

Raw: node.raw.banIp

Bans for future connects. who is an IP string (banIp), a directory account id number (banAccount, wire v17: the ban follows the account to any address) or a Player (banPlayer: account when verified, IP always, and the player is kicked). Persisted in bans.json. An existing session stays for the first two forms.

Raw: node.raw.unbanIp

Lifts a ban: an IP string, or an account id number. False when it was not banned.

Raw: node.raw.isBanned

Whether an IP string, an account id number, or a Player (its account when verified, else its IP) is banned.

Raw: node.raw.getBans

Every ban: IP bans carry ip, account bans carry account (the directory’s id).

A per-resource JSON store (storage/<resource>.json), flushed atomically on every write.

Raw: node.raw.storageGet

The stored value, or default when the key is absent.

Raw: node.raw.storageSet

Stores any JSON-serialisable value; flushed atomically.

Raw: node.raw.storageDelete

Removes a key; false when absent.

Files inside the resource’s own folder; paths that escape it are rejected.

Raw: node.raw.readFile

Reads a file inside the resource folder; nil when missing or outside it.

Raw: node.raw.writeFile

Writes a file inside the resource folder, creating folders. Synchronous.

Raw: node.raw.writeFileAsync

Writes off the worker; cb(ok) runs on the worker when done. true means accepted.

Raw: node.raw.listFiles

Lists a folder inside the resource folder (the root when omitted).

Asynchronous HTTP. Callbacks run on the worker; fetch is the coroutine form for use inside node.async.

Raw: node.raw.httpGet

GET; cb(status, body, headers) on the worker: status is the HTTP status code, or -1 with the error text in body when the request failed (could not resolve or connect, TLS handshake or certificate verification, timeout, malformed or oversized response). Returns false when the request could not be queued – cb then never runs. About 15 s timeout, 8 MB body. TLS peer verification is off unless the hoster set [Http] CaFile (then every https request is verified against that bundle and a failure is a -1).

Raw: node.raw.httpPost

POST; body is a table (JSON-encoded) or a string. cb and the return value are as for get: status -1 with the error text in body when the request failed, false when it could not be queued.

Raw: node.raw.httpRequest

Any method (server 1.2.0): node.http.request("PUT", url, { headers = h, body = t }, cb). method is a token of letters A-Z only, at most 16 – "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD" or a custom one the service understands; the prelude upper-cases it for you, anything else comes back as status -1 with body “invalid HTTP method”; opts.headers is a table of header -> value, opts.body a table (JSON-encoded) or a string, both optional (opts itself may be nil). cb(status, body, headers) and the return value are exactly as for get: the HTTP status code, or -1 with the error text in body when the request failed; false when the request could not be queued (cb then never runs). A HEAD answer carries status and headers and an empty body. put, patch, delete and head are this with the method fixed.

Raw: node.raw.httpRequest

PUT; body is a table (JSON-encoded), a string, or nil for none. cb and the return value are as for get.

Raw: node.raw.httpRequest

PATCH; body is a table (JSON-encoded), a string, or nil for none. cb and the return value are as for get.

Raw: node.raw.httpRequest

DELETE; the body is optional (most services want none: node.http.delete(url, nil, headers, cb) or node.http.delete(url, cb)). cb and the return value are as for get.

Raw: node.raw.httpRequest

HEAD; cb(status, “”, headers) – the status and the response headers, never a body. The return value is as for get.

The coroutine form, inside node.async only: local status, body, headers = node.http.fetch(url, { method = "DELETE", body = t, headers = h }). opts.method is any method request accepts (default GET; before 1.2.0 anything but POST was sent as GET). status is the HTTP status code, or -1 with the error text in body when the request failed; it is 0, with body “request not queued”, only when the request could not be queued at all (the background pool is full or shutting down).

Asynchronous, pooled PostgreSQL over the connection string in [Database] Url (enabled() is false without one). Every call has two forms: with a callback, which runs on the worker when the statement completes, or without one inside node.async, where it suspends the coroutine and returns the result – local result, err = node.pg.query(sql, params). Statements are parameterised ($1..$n, node.pg.NULL for NULL), rows come back keyed by column name, errors are tables with the SQLSTATE or an internal code in code. tx runs a function on one reserved connection between BEGIN and COMMIT/ROLLBACK.

Raw: node.raw.pgEnabled

true when the server was configured with a database ([Database] Url in server.toml, or NODE_DATABASE_URL). false means every node.pg call answers err.code == "pg_disabled"; check it once at load and degrade gracefully instead of assuming a database.

Raw: node.raw.pgReady

true while at least one pool connection is established. The pool connects in the background and reconnects on its own (0.5 s to 30 s backoff); the server starts without the database, and until this turns true statements fail at once with err.code == "08001" rather than waiting.

The NULL parameter. A Lua nil inside the params array ends the array – { 1, nil, "x" } sends one parameter – so write { 1, node.pg.NULL, "x" } for every NULL. One opaque value per server, recognised by identity; it belongs only inside a params array. Coming back the other way, a NULL cell is simply absent from its row (row.col == nil).

node.pg.query(sql, params?, cb?) -> record{rows,count,columns}?, record{code,message,detail,hint,constraint,table}?

Заголовок раздела «node.pg.query(sql, params?, cb?) -> record{rows,count,columns}?, record{code,message,detail,hint,constraint,table}?»

Raw: node.raw.pgQuery

Runs one parameterised statement on the pool. sql uses $1..$n; params is the array of values, in order: node.pg.NULL -> NULL (a nil would end the array), boolean, integer, float, string as text (a NUL inside is not supported – send bytea as hex through decode($1, 'hex')), a table as JSON (for json/jsonb columns); the type is inferred by the server. A statement without parameters may leave params out: node.pg.query(sql, cb). Never concatenate values into sql. Two forms. With cb, the callback form: returns nothing and cb(result, err) runs on the worker when the statement completes. Without cb, inside node.async, the suspending form: the coroutine suspends and local result, err = node.pg.query(sql, params) gets the same two values; outside a coroutine it raises “node.pg.query: callback required outside node.async” (like node.sleep). result = { rows = { { col = value, ... }, ... }, count = <rows returned, or affected for a statement without rows>, columns = { "col", ... } }. Rows are keyed by column name (alias duplicate names; columns keeps the order); values follow the column type – bool -> boolean, int2/int4/int8 -> integer (64-bit), float4/float8 -> number, numeric -> string (exact; keep money in integer minor units), text/varchar/uuid/timestamp/date/time/interval -> string (Postgres’ text form), json/jsonb -> string (node.json.decode it), bytea -> string of raw bytes, arrays and anything else -> Postgres’ text form; a NULL cell is absent (nil). err = { code, message, detail?, hint?, constraint?, table? } with the SQLSTATE in code ("23505" unique violation, with constraint; "42601" syntax; "57014" statement timeout, [Database] QueryTimeoutMs; "08001" no connection; "08006" connection lost) or an internal code: pg_disabled (no [Database] Url), pg_queue_full (1000 statements pending), pg_result_cap (more than [Database] MaxRows rows; the result is dropped), pg_params (a value inside params the driver cannot send – a function, a coroutine, a userdata other than node.pg.NULL, a string with a NUL, a table JSON cannot encode – or more than 1000 of them), pg_reload (never delivered: a resource unloaded while a statement is in flight never sees its callback; the code exists so the list is complete). A params that is not a table at all – or a sql that is not a string, or a cb that is not a function – is a programming error and raises at the caller’s line instead of reaching cb. Every statement here is its own transaction; see tx for several on one connection – a node.pg.query inside a tx function is NOT part of that transaction.

node.pg.exec(sql, params?, cb?) -> number?, record{code,message,detail,hint,constraint,table}?

Заголовок раздела «node.pg.exec(sql, params?, cb?) -> number?, record{code,message,detail,hint,constraint,table}?»

Raw: node.raw.pgQuery

query for statements whose rows you do not want: no row is materialised and the answer is the count – rows affected by an INSERT/UPDATE/DELETE, rows a SELECT would have returned. cb(count, err) in the callback form; local count, err = node.pg.exec(sql, params) inside node.async. Same params, errors and rules as query.

node.pg.tx(fn, cb?) -> any..., record{code,message,detail,hint,constraint,table}?

Заголовок раздела «node.pg.tx(fn, cb?) -> any..., record{code,message,detail,hint,constraint,table}?»

A transaction. fn(tx) runs as a coroutine on ONE pool connection reserved for it – its own node.async task in the callback form, the caller’s coroutine in the suspending form: BEGIN, then everything fn does through tx, then COMMIT when fn returns normally, ROLLBACK when fn raises or calls tx:rollback(). Inside fn use the suspending forms only: local result, err = tx:query(sql, params?) and local count, err = tx:exec(sql, params?) – the same contract as node.pg.query/exec without a callback, on the reserved connection; tx:rollback(reason?) raises out of fn and rolls back. A plain node.pg.query inside fn goes to ANOTHER connection and is NOT part of the transaction. After a failed statement Postgres refuses every further statement of the transaction (25P02) until it is rolled back – check err and tx:rollback() or raise. Outcome: fn returned normally and COMMIT succeeded -> the return values of fn; COMMIT failed -> nil, err (a SQLSTATE; Postgres rolled back); fn raised -> ROLLBACK and nil, { code = "rollback", message = <the error text, or the .message of a raised table>, cause = <the value fn raised> } (so error(err) with the err of a failed tx:query keeps its SQLSTATE in cause.code); tx:rollback(reason) -> ROLLBACK and nil, { code = "rollback", message = reason }; the transaction outlived [Database] TxTimeoutMs (default 30 s) -> the server rolled it back already and the next tx call or the COMMIT answers { code = "tx_timeout" }; BEGIN itself failed -> nil, err (08001, pg_disabled, pg_queue_full). With cb, cb(…) receives exactly those values on the worker (cb(<returns of fn>) or cb(nil, err)); without cb, inside node.async, local a, b = node.pg.tx(fn) suspends the caller and returns them; outside a coroutine the suspending form raises “node.pg.tx: callback required outside node.async”. Keep transactions short: the connection is unavailable to everyone else while fn runs, and a node.sleep inside fn holds it too. Unloading the resource rolls back its open transactions and drops their callbacks.

JSON in and out. Most of the API encodes for you (a table given to send, broadcast, bus.emit, storage.set is encoded); these are for everything else.

Raw: node.raw.jsonEncode

Encodes a Lua value as JSON.

Raw: node.raw.jsonDecode

Decodes JSON text; nil on a parse error.

Digests and randomness.

Raw: node.raw.sha256

SHA-256 as 64 hex characters.

Raw: node.raw.hmac

HMAC-SHA256 as hex.

Raw: node.raw.randomBytes

n cryptographically random bytes as a binary string (1..65536).

n random bytes (default 16) as hex – a token.

node.log(msg, ...) prints under the resource’s tag with string.format arguments; the table also carries the levels and the raw forms.

Raw: node.raw.logWarn

Logs at the warning level under this resource’s name; format arguments accepted.

Raw: node.raw.logError

Logs at the error level under this resource’s name.

Raw: node.raw.logTag

Logs under one of the server’s own tags (Core, Net, Res, Mods, Module, Join, Leave, Kick, Veh, Warn, Error, Debug).

Raw: node.raw.logCustom

Logs under a tag of your own in a 0xRRGGBB colour.

Raw: node.raw.logRaw

Writes text to the console as-is, no timestamp or tag.

Raw: node.raw.setLogSink

Observes every server log line as fn(tag, level, msg) on the worker; nil clears. Cannot suppress lines.

Raw: node.raw.setConsoleTitle

Sets the console window title.

Publish/subscribe between resources on this server (and native modules). Asynchronous, delivered to the sender too.

Raw: node.raw.emitResource

Publishes to every resource and native module subscribed to name (the sender included), asynchronously on the worker. data is a table (JSON-encoded) or a string.

Raw: node.raw.onResource

Subscribes fn(sourceResourceName, data) to bus messages under name; a native module appears as “native”.

Raw: node.raw.offResource

Unsubscribes this resource’s bus handlers for name – all of them, or only fn, matched by identity from any context, including coroutines.

Raw bytes on a numbered channel, for native modules and their client halves.

Raw: node.raw.onModule

Subscribes fn(player, data) to raw bytes clients send on a u32 channel.

Raw: node.raw.offModule

Unsubscribes this resource’s handlers for the channel – all of them, or only fn, matched by identity from any context, including coroutines.

Raw: node.raw.sendModule

Sends raw bytes to a Player (or id), or to everyone with target “all” or nil.

The packet filter and its cache. Visibility groups (Player:setGroup, Vehicle:setGroup) are the cheap alternative for room-style rules.

Raw: node.raw.on

Installs a relayRequest hook (the same thing as node.on("relayRequest", fn)): fn(fromPid, toPid, category, subtype, globalId) returns false to hide a packet from that recipient. Runs with ids on the hot path; verdicts are cached until invalidate. Prefer visibility groups for room-style rules.

Raw: node.raw.off

Removes this resource’s hooks, or only fn.

Raw: node.raw.invalidateRelayCache

Drops every cached verdict so the hooks are consulted again. Call it whenever the data a hook reads has changed.

The resource’s own manifest and settings, and reloading resources by name.

Raw: node.raw.reloadResource

Queues a reload of a resource by name – registrations, timers and coroutines dropped, the folder loaded again. true means accepted. A resource may reload itself.

node.resources.manifest() -> record{name,version,type,server,client,config}?

Заголовок раздела «node.resources.manifest() -> record{name,version,type,server,client,config}?»

Raw: node.raw.getManifest

This resource’s resource.toml as a table, re-read from disk.

Speak into the chat as the server. Needs the chat resource (it owns the screen side); silent without it.

Says a system line in everyone’s chat (via the chat resource). Format arguments accepted.

Says a system line in one player’s chat; target is a Player or an id.

Chat commands: a line starting with / reaches the handler registered for its first word, with the sender as a Player. Needs the chat resource.

Registers a chat command: a line /name ... typed by any player calls fn(player, args, raw) with the sender as a Player, the words after the name as an array and the whole line. opts.role restricts it to players whose role equals it; others are told they may not. Names are case-insensitive. Needs the chat resource, which publishes the lines.

Unregisters a command.