API reference
Every tDB export, the oxmysql compatibility surface, the MySQL.lua library, console commands and events.
Every name below is reachable as exports.tDB:<name>, exports.oxmysql:<name>,
exports['mysql-async']:<name> and exports.ghmattimysql:<name>.
Exports marked async return a promise: use the _async name, or Citizen.Await.
Readiness
| Export | Parameters | Returns | Async |
|---|---|---|---|
isReady | none | boolean | no |
onReady | callback | nil. Fires immediately if already ready, else queues | no |
isClientAuthenticated | none | boolean, live rather than the boot result | no |
getUrl | none | string base URL, or nil before ready | no |
awaitConnection | none | true | yes |
A record or collection call made too early can still throw PocketBase client not ready yet.
Wrap the first one:
exports.tDB:onReady(function()
local players = exports.tDB:getFullList('players')
end)oxmysql exports
Each base name is registered three times: name, name_async and nameSync. All three
behave identically.
Accepted call shapes: (sql), (sql, params), (sql, callback), (sql, params, callback).
With a callback the export returns nothing and invokes it; without one it returns a promise.
| Export | Returns |
|---|---|
query | array of rows |
execute | array of rows. Alias of query |
fetch | array of rows. Alias of query |
single | first row, or nil |
scalar | first column of the first row, or nil |
insert | the new row's id |
update | affected row count |
prepare | one result per parameter set; a single set returns bare, and a callback unwraps a one-column row to the value |
rawExecute | as prepare, without the unwrapping |
transaction | true, or false if it rolled back. Never throws |
store | the query string unchanged. Synchronous, and works before readiness |
mysql-async aliases
| Alias | Behaves as | Returns |
|---|---|---|
mysql_execute | update | affected row count |
mysql_insert | insert | insert id |
mysql_fetch_all | query | rows |
mysql_fetch_scalar | scalar | scalar |
mysql_transaction | transaction | true/false |
mysql_store | store | the query |
Query cache
Off by default. When on, row queries are cached and any write through tDB clears the cache.
| Export | Parameters | Returns |
|---|---|---|
enableCache | ttl = 60000, maxSize = 100 | true |
disableCache | none | true, and clears |
invalidateCache | pattern?: substring match on the key; omit to clear all | true |
setSlowQueryThreshold | ms: 0 disables the warning; default 150 | true |
lib/MySQL.lua
Add server_script '@tDB/lib/MySQL.lua' to your fxmanifest.lua. Every entry has an
.await form and a callable callback form, and waits for the database before dispatching,
so a script querying during its own startup gets rows.
| Entry | Returns |
|---|---|
MySQL.query | rows |
MySQL.single | row or nil |
MySQL.scalar | value |
MySQL.insert | insert id |
MySQL.update | affected rows |
MySQL.execute | rows (alias of query) |
MySQL.fetch | rows |
MySQL.prepare | per-set results |
MySQL.rawExecute | raw result |
MySQL.transaction | true/false |
local rows = MySQL.query.await('SELECT * FROM players')
local id = MySQL.insert.await('INSERT INTO logs (msg) VALUES (?)', { 'joined' })
MySQL.ready(function() print('database up') end)Legacy namespaces: MySQL.Sync.execute (affected rows), MySQL.Sync.insert,
MySQL.Sync.fetchAll, MySQL.Sync.fetchSingle, MySQL.Sync.fetchScalar,
MySQL.Sync.transaction, MySQL.Sync.prepare, and the matching MySQL.Async.* callback
forms. Also MySQL.ready, MySQL.isReady, MySQL.awaitConnection and MySQL.store.
Records
| Export | Parameters | Returns | Async |
|---|---|---|---|
create | collection, data, options? | created record | yes |
update | collection, id, data, options? | updated record | yes |
delete | collection, id?, options? | true | yes |
getOne | collection, id, options? | record. Throws on 404 | yes |
getList | collection, page = 1, perPage = 30, options? | { page, perPage, totalItems, totalPages, items } | yes |
getFullList | collection, options? | array of records | yes |
getFirstListItem | collection, filter, options? | record. Throws on 404 | yes |
findRecordsByIds | collection, ids | array of records | yes |
countRecords | collection, filter?, params? | number | yes |
filter | rawFilter, params? | escaped filter string | no |
Pass SQL as the first argument to update or delete and the call goes down the oxmysql
path instead.
Build every filter with filter(), so values are bound rather than interpolated into the
string:
local f = exports.tDB:filter('score > {:min} && banned = {:banned}', { min = 100, banned = false })
local top = exports.tDB:getFullList('players', { filter = f })
local one = exports.tDB:getFirstListItem('players', f)Collections
| Export | Parameters | Returns | Async |
|---|---|---|---|
getCollections | options? | array | yes |
getCollection | idOrName, options? | collection | yes |
createCollection | data, options? | collection | yes |
updateCollection | idOrName, data, options? | collection | yes |
deleteCollection | idOrName, options? | true | yes |
truncateCollection | idOrName, options? | true | yes |
importCollections | collections (table or JSON string), deleteMissing = false, options? | true | yes |
getCollectionScaffolds | options? | scaffold map | yes |
createMigrationHelper | none | table of createCollection, updateCollection, deleteCollection closures | no |
exports.tDB:createCollection({
name = 'players',
type = 'base',
fields = {
{ name = 'username', type = 'text', required = true },
{ name = 'score', type = 'number' },
{ name = 'banned', type = 'bool' },
},
})Realtime
| Export | Parameters | Returns | Async |
|---|---|---|---|
subscribe | collection, topic, callback? | true | yes |
unsubscribe | collection, topic?: omit to drop all for the collection | true | yes |
subscribeToTopic | topic, callback? | true | yes |
unsubscribeFromTopic | topic | true | yes |
unsubscribeByPrefix | topicPrefix | true | yes |
isRealtimeConnected | none | boolean | no |
sendRealtimeMessage | topic, data | number of clients sent to | yes |
getRealtimeClients | none | array of { id, authenticated, authId } | yes |
Subscribe, then handle the event tDB raises. The name is
pocketbase:<collection>:<topic> for a collection and pocketbase:topic:<topic> for a
custom one:
exports.tDB:subscribe('bans', '*')
AddEventHandler('pocketbase:bans:*', function(data)
if data.action == 'create' then print(data.record.id) end
end)Transactions and batches
| Export | Parameters | Returns | Async |
|---|---|---|---|
runTransaction | operations: { type = "create"|"update"|"delete", collection, id?, data? } | array: { id } per create/update, { deleted = true } per delete | yes |
batch | none | batch handle | no |
batchCreate | batch, collection, data, options? | the handle, chainable | no |
batchUpdate | batch, collection, id, data, options? | the handle | no |
batchDelete | batch, collection, id, options? | the handle | no |
batchUpsert | batch, collection, data, options? | the handle | no |
batchSend | batch | batch result array | yes |
runTransaction runs inside a real database transaction and needs no setting. The batch*
exports are PocketBase's batch API and must be enabled in Admin → Settings → Batch API
first.
Raw SQL
The same database the oxmysql exports use, with {:name} placeholders and no MySQL
translation — write SQLite here. All async.
| Export | Parameters | Returns |
|---|---|---|
sqlQuery | sql, params? | array of rows |
sqlSingle | sql, params? | first row, or nil |
sqlScalar | sql, params?, columnName? | the value, or nil |
sqlExecute | sql, params? | insert id only |
sqlTransaction | queries: { sql, params?, expect? } | true; throws and rolls back the whole batch on failure |
Integers arrive as numbers and NULL arrives as nil, not an empty string.
local row = exports.tDB:sqlSingle('SELECT * FROM players WHERE id = {:id}', { id = 1 })
exports.tDB:sqlTransaction({
{ sql = 'UPDATE accounts SET balance = balance - {:n} WHERE id = {:a}', params = { n = 100, a = 'a' } },
{ sql = 'UPDATE accounts SET balance = balance + {:n} WHERE id = {:b}', params = { n = 100, b = 'b' } },
})Auth
All async. Every one takes the auth collection name first.
| Export | Parameters | Returns |
|---|---|---|
listAuthMethods | collection, options? | auth methods |
authWithPassword | collection, usernameOrEmail, password, options? | { token, record } |
authCollectionWithPassword | same | { token, record } |
authWithOTP | collection, otpId, password, options? | { token, record } |
authWithOAuth2Code | collection, provider, code, codeVerifier, redirectURL, createData?, options? | { token, record } |
authRefresh / authRefreshCollection | collection, options? | { token, record } |
requestOTP | collection, email, options? | { otpId } |
requestPasswordReset | collection, email, options? | true |
confirmPasswordReset | collection, token, password, passwordConfirm, options? | true |
requestVerification | collection, email, options? | true |
confirmVerification | collection, token, options? | true |
requestEmailChange | collection, newEmail, options? | true |
confirmEmailChange | collection, token, password, options? | true |
listExternalAuths | collection, recordId, options? | array |
unlinkExternalAuth | collection, recordId, provider, options? | true |
Files and mail
| Export | Parameters | Returns | Async |
|---|---|---|---|
getFileUrl | record, filename, options? | URL string | no |
getFileToken | options? | token string | yes |
sendEmail | to (string or array), subject, html, text? | true | yes |
sendEmail needs mail configured in the admin UI — see Mail.
Commands
Server console only, all gated on the command.tdb ACE. See
Importing .sql files for what each does in full.
| Command | Notes |
|---|---|
tdb sql status | Every .sql file with its recorded state, plus ledger rows whose file is gone |
tdb sql import <file> | Applies one file now |
tdb sql import <file> --force | --force is recognised anywhere in the arguments |
tdb sql tables [match] | Every table with its row count, optionally filtered by substring |
tdb sql query <sql> | Runs a read query and prints the rows, 50 at a time |
tdb sql plan | Which tables would be handed to PocketBase, and how each id is handled |
tdb sql adopt | Hands them over now, after taking a backup |
Anything else prints
usage: tdb sql status | import <file> [--force] | tables [match] | query <sql> | plan | adopt.
Before the database is ready, every subcommand prints
not ready yet - wait for the startup banner.
Events
All server-local. Use AddEventHandler.
| Event | Payload |
|---|---|
pocketbase:<collection>:<topic> | { action = "create"|"update"|"delete", record } |
pocketbase:topic:<topic> | whatever was published to the topic |
tdb:startupStatus | a single string: the rendered startup box |
Version facts
| Fact | Value |
|---|---|
| Resource version | 1.0.0 |
| PocketBase | 0.40.4, fetched and checksum-verified into .runtime/ |
| tLib minimum | 1.0 (tlib_min_version) |
| Node | node_version '22' |
| Provides | oxmysql, mysql-async, ghmattimysql |
| Platforms | windows_amd64, linux_amd64 |
tDB
Still stuck? Support can answer it directly.
Was this page helpful?
TIMMYG Studios