Schema
Define your database tables in TypeScript as the source of truth for migrations, Lua refs, and ORM metadata.
Your schema is defined once in src/schema.ts. From that file, KuraDB generates:
- SQL migrations
- Lua row and insert/update types
kura.db.tables.<tableName>metadata- generated column refs such as
kura.db.tables.users.id
Examples in this page use a users table only as an illustration. The actual generated names always come from your own schema.ts.
When you only need updated Lua autocomplete, run kuradb generate --types-only from the extracted release directory or your local resource root. That regenerates lib/schema.generated.lua without creating a migration or starting FiveM.
schema.generated.lua is the LuaLS-facing artifact. It contains:
- row types such as
UsersRow - insert/update shapes such as
UsersInsertandUsersUpdate - generated table definitions under
kura.db.tables - generated column refs under
table.columnRefsand direct fields such asusers.id
Columns
Use defineColumn to declare a column:
import { defineColumn } from './orm';
const id = defineColumn('id', 'uuid', { primaryKey: true });
const username = defineColumn('username', 'string');
const deletedAt = defineColumn('deleted_at', 'date', { nullable: true });
const createdAt = defineColumn('created_at', 'date', { defaultExpression: 'NOW()' });The first argument is the SQL column name. The key you assign it to in the table object becomes the TypeScript/Lua field name.
Example:
createdAt: defineColumn('created_at', 'date')
passwordHash: defineColumn('password_hash', 'string')That generates Lua-facing fields:
users.createdAt
users.passwordHashColumn types
| Kind | PostgreSQL | TypeScript | Lua |
|---|---|---|---|
'uuid' | UUID | string | string |
'string' | VARCHAR(255) | string | string |
'number' | INTEGER | number | number |
'bigint' | BIGINT | bigint | number |
'boolean' | BOOLEAN | boolean | boolean |
'date' | TIMESTAMP | Date | number |
'json' | JSONB | object | table |
'custom' | TEXT | unknown | any |
Column options
| Option | Type | Description |
|---|---|---|
primaryKey | boolean | Marks the column as PRIMARY KEY |
nullable | boolean | Allows NULL |
defaultExpression | string | Raw SQL default such as 'NOW()' |
encode | function | Transform a value before writing |
decode | function | Transform a value after reading |
Custom encode / decode
Use hooks for values that need transformation between TypeScript and SQL:
const metadata = defineColumn<Record<string, unknown>>('metadata', 'json', {
nullable: true,
encode: (value) => JSON.stringify(value),
decode: (raw) => typeof raw === 'string' ? JSON.parse(raw) : raw,
});
const createdAt = defineColumn<Date>('created_at', 'date', {
encode: (value) => value.toISOString(),
decode: (raw) => new Date(raw as string),
});Tables
Use defineTable to group columns:
import { defineTable, defineColumn } from './orm';
export const users = defineTable('public', 'users', {
id: defineColumn('id', 'uuid', { primaryKey: true }),
username: defineColumn('username', 'string'),
email: defineColumn('email', 'string'),
passwordHash: defineColumn('password_hash', 'string'),
createdAt: defineColumn('created_at', 'date'),
updatedAt: defineColumn('updated_at', 'date'),
deletedAt: defineColumn('deleted_at', 'date', { nullable: true }),
});Arguments:
- Schema name, usually
'public' - SQL table name
- Object mapping Lua/TypeScript field names to column definitions
The primaryKey list is derived automatically from columns with primaryKey: true.
Schema
Group all tables into a schema export:
import { defineSchema, defineTable, defineColumn } from './orm';
export const users = defineTable('public', 'users', { /* ... */ });
export const posts = defineTable('public', 'posts', { /* ... */ });
export const schema = defineSchema('public', {
users,
posts,
});The schema object is consumed by:
kuradb generatekuradb generate --types-onlykuradb migrate- the Lua type generator
- the Lua query builder metadata under
kura.db.tables
Generated Lua usage
For the best Lua autocomplete and refactor safety, use the generated table definitions and column refs:
local users = kura.db.tables.users
local rows = kura.db.select({ users.id, users.createdAt, users.passwordHash })
:from(users)
:where(op.isNull(users.deletedAt))
:await()
print(rows[1].createdAt)
print(rows[1].passwordHash)Builder results use your Lua field names when schema metadata is available. If your SQL column is created_at, the builder exposes it as createdAt in returned rows.
String-based usage still works:
local rows = kura.db.select()
:from('users')
:where(op.isNull('deleted_at'))
:await()Generated metadata shape
The generated Lua artifact includes both string maps and richer column refs:
local users = kura.db.tables.users
users.columns.createdAt -- "created_at"
users.columnRefs.createdAt.sqlName -- "created_at"
users.columnRefs.createdAt.luaName -- "createdAt"
users.createdAt -- same generated column ref objectThis is why both of these work:
op.eq(users.createdAt, os.time())
op.eq('created_at', os.time())Generated refs also carry the Lua alias that the builder returns in result rows. If your schema defines createdAt = defineColumn('created_at', 'date'), then:
local users = kura.db.tables.users
local row = kura.db.select({ users.createdAt })
:from(users)
:single()
:await()
print(row.createdAt)The builder compiles the SQL column name created_at, then aliases it back to the Lua field name createdAt in the returned row.
Aliases vs raw SQL
Generated schema aliases such as users.createdAt are supported directly. Arbitrary SQL expressions or custom aliases are not supported through plain string column names. Use explicit raw SQL when you need expression-level control.
Full example
A complete src/schema.ts:
import { defineColumn, defineSchema, defineTable } from './orm';
export const characters = defineTable('public', 'characters', {
id: defineColumn('id', 'uuid', { primaryKey: true }),
ownerId: defineColumn('owner_id', 'uuid'),
firstName: defineColumn('first_name', 'string'),
lastName: defineColumn('last_name', 'string'),
dateOfBirth: defineColumn('date_of_birth', 'date'),
cash: defineColumn('cash', 'number'),
bank: defineColumn('bank', 'number'),
metadata: defineColumn('metadata', 'json', { nullable: true }),
createdAt: defineColumn('created_at', 'date', { defaultExpression: 'NOW()' }),
deletedAt: defineColumn('deleted_at', 'date', { nullable: true }),
});
export const vehicles = defineTable('public', 'vehicles', {
id: defineColumn('id', 'uuid', { primaryKey: true }),
ownerId: defineColumn('owner_id', 'uuid'),
model: defineColumn('model', 'string'),
plate: defineColumn('plate', 'string'),
garage: defineColumn('garage', 'string'),
fuel: defineColumn('fuel', 'number'),
properties: defineColumn('properties', 'json', { nullable: true }),
});
export const schema = defineSchema('public', {
characters,
vehicles,
});