Modern-first Lua 5.4 utility library for FiveM resources.
- ESX client/server helpers
- Shared cooldown state — by position or by identifier
- Runtime schema validation
- Reactive state tracking (
state,effectwith disposers,computed) - Hook pipeline: validated event handlers with composable middleware
- Namespaced logger with configurable levels
- ox_lib keybind wrapper with idempotent registration
- Lua-driven NUI rendering with
lui, hybrid declarative node trees, motion, shadcn-like components, theme overrides, and runtime utility styles - Text and Iconify-powered LUI icons through ergonomic Lua props
- Database module with
count,select,orderBy,limit/offset,upsert,bulkInsert,transaction, and ALTER-awaredb push - Console diagnostics via
<resource>:doctor - Drop-in Language Server type stubs in
library/
Add the shared loader to resources that use this library:
shared_script '@sure_lib/init.lua'Modules are loaded through sure.getModule(moduleName).
Available modules: esx, player, cooldown, validator, track, hook, log, slice, config, spawn, keybind, db, lui.
listenerwas renamed tohookand now supports ause(middleware)pipeline and explicitdispatch/dispatchClient/dispatchSerververbs.
Other resources can inject middleware into a hook through exports:
-- from another resource that depends on sure_lib
local hook = sure.getModule('hook')
hook:injectResource('resourceWithHook', 'hookName', function(ctx)
ctx.args[1] = ctx.args[1]:upper() -- mutate
-- ctx.cancelled = true -- short-circuit
end)The target resource must be started before injectResource runs.
slice packages state, actions, event handlers, and lifecycle hooks for a feature in one declarative spec. Every name is auto-prefixed with the slice name so events, commands, and logs stay consistent across resources.
local slice = sure.getModule('slice')
return slice 'duty' {
state = {
onDuty = false,
streak = 0,
},
actions = {
toggle = function(s)
s.state.onDuty = not s.state.onDuty
end,
},
net = {
sync = function(s, value)
s.state.onDuty = value
end,
},
watch = {
onDuty = function(s, value)
s.log.info('changed to ' .. tostring(value))
s:emit('changed', value)
end,
},
commands = {
print = function(s)
print(json.encode(s:snapshot()))
end,
},
onLoad = function(s)
s.log.info('ready')
end,
}statebecomes a reactive proxy; writing triggerswatchandsubscribehandlers.actionsare pure mutators exposed asslice.actions.<name>(...).on/netregister listeners against'<sliceName>:<eventName>'.emit/emitClient/emitServerauto-prefix the same way.commandsregisters<sliceName>:<commandName>console commands.onLoad/onUnloadonly fire for the current resource.everyspawns a single scheduling thread per slice that fires each interval handler when its window elapses, sleeping only until the next due interval.- Auto-generates a
setXaction for every state key. Custom actions with the same name take precedence. slice:transaction(fn)batches mutations so each watcher fires once with the net change.netSync = { stateKey = 'sender' | 'receiver' | { direction, scope, diff } }mirrors state across server/client. Scopes are managed throughslice:scope(name)withadd/remove/list/containsaccepting both player ids and ESX identifiers. Settingdiff = trueswitches the wire format to a keyed-array patch ({ added, removed, changed }) and skips emits that produce an empty patch. Removing a player from a scope emits{ cleared = true }to that player for every diff field, which clears the receiver's mirror and letsrefcleanup tear down world entities.
slice:ref(stateKey, fn) watches an array of items, calls fn(item, index) for every new item, and re-runs only when an individual item's content changes (deep equal). Removed items run their cleanup function; unchanged items are left alone. Duplicate key values raise an error.
return slice 'world' {
state = {
entities = {
{ key = 'guard-1', entity = 0, coords = vector3(0, 0, 0) },
},
},
onLoad = function(s)
s:ref('entities', function(item)
local ped = createGuard(item.coords)
return function()
DeleteEntity(ped)
end
end)
end,
}- Each item must expose a
keyfield (stringornumber). - Return a function from
fnto register a cleanup; omit the return for no-op cleanup. refreturns adisposefunction that unmounts every active item and stops watching. Every active ref also auto-disposes ononResourceStopfor the current resource, afterspec.onUnloadruns.- Array mutation helpers
slice:push(stateKey, item),slice:patch(stateKey, itemKey, partial), andslice:removeBy(stateKey, predicate)filter + reassign the array so watchers and netSync diffs fire correctly without hand-written loops. slice:interact(stateKey, spec)(client only) bundlesslice:refwithsure.spawnandlib.points.new: spawns the world entity fromspec.spawn, creates a proximity point, wiresonEnter/onExit/nearbycallbacks with(slice, item, ctx), and cleans up the entity + point on unmount. Prompts/UI are left to the user.- The watcher fires when the array reference changes — assign a new table to
state.<key>to trigger reconciliation.
For Lua UI, point your resource NUI page at the bundled renderer:
ui_page 'https://cfx-nui-sure_lib/web/lui/index.html'Enable LUI debug traces with ?luiDebug=1 or localStorage.setItem('sure:lui:debug', '1') in NUI Devtools.
lui supports both nested callback builders and declarative node factories, so larger interfaces can be split into small Lua component functions.
The repo ships LLS stubs in library/. Add the folder to your .luarc.json:
{
"runtime.version": "Lua 5.4",
"workspace.library": ["../sure_lib/library"],
"diagnostics.globals": ["sure", "lib", "cache", "exports"]
}See library/README.md for details.
<resource>:db push <schemaName>—CREATE TABLE IF NOT EXISTSorALTER TABLE ... ADD COLUMNfor new fields.<resource>:db pull <tableName>— write a schema file from a live table.sure_lib:doctor— verifies thatox_lib,es_extended,oxmysql, and the requiredlib.*helpers are reachable.
Documentation: https://docs.sure-developer.com
