NormModel class
A model is your handle for a table: CRUD, query entry points, hooks, scopes, and schema.
Auto-generated
This page is generated from the source annotations by scripts/gen-api.mjs. Edit the LuaCATS doc comments in the Norm sources, not here.
| Member | Returns | Description |
|---|---|---|
all | NormRecordListPromise (async) | Resolves with every record in the table. |
autoincrement_pk | — | |
avg | NormNumberPromise (async) | AVG of a column across the whole table. |
build | NormRecord | Build an unsaved record from a data table (nothing hits the database until |
columns | — | |
columns_by_name | — | |
count | NormNumberPromise (async) | Resolves with the total row count of the table. |
create | NormRecordPromise (async) | Build and immediately INSERT a record. |
find | NormRecordOrNilPromise (async) | Find a single record by its primary key. |
find_by | NormRecordOrNilPromise (async) | Find the first record matching a { column = value } filter (ANDed). |
find_or_create | NormRecordPromise (async) | Find the first record matching attributes; if none exists, INSERT one built |
find_or_new | NormRecordOrNilPromise (async) | Find the first record matching attributes; if none exists, return an |
group_by | NormQueryBuilder | |
having | NormQueryBuilder | |
hook | self: NormModel | Register a lifecycle hook handler. |
hooks | — | event -> { fn, ... |
indexes | — | Indexes emitted at sync(). |
insert_many | NormNumberPromise|NormRecordListPromise (async) | Bulk-insert many rows in ONE statement. |
join | NormQueryBuilder | |
left_join | NormQueryBuilder | |
limit | NormQueryBuilder | |
max | NormNumberPromise (async) | MAX of a column across the whole table. |
min | NormNumberPromise (async) | MIN of a column across the whole table. |
omit | NormQueryBuilder | |
only_trashed | NormQueryBuilder | Start a query over ONLY soft-deleted rows (soft-delete models only). |
order | NormQueryBuilder | dir: |
orm | — | |
paginate | NormPromise (async) | Paginate the whole table. |
parse | any | Convert a raw driver value into a Lua value for the given column (decode). |
primary_key | — | |
query | NormQueryBuilder | Start a chainable query against this model's table. |
record_class | — | A single row. |
relations | — | |
scope | self: NormModel | Register a reusable, named query fragment. |
scopes | — | name -> fn(query, ...), reusable query fragments |
select | NormQueryBuilder | |
select_raw | NormQueryBuilder | |
serialize | any | Convert a Lua value into something storable for the given column (encode). |
soft_deletes | — | The soft-delete column name (nil if disabled). |
sum | NormNumberPromise (async) | SUM of a column across the whole table. |
sync | NormBooleanPromise (async) | Create this model's table (CREATE TABLE IF NOT EXISTS). |
table | — | |
timestamps | — | Auto-managed timestamp columns (nil if disabled). |
update_or_create | NormRecordPromise (async) | Find the first record matching attributes and UPDATE it with values; if |
upsert | NormRecordOrNilPromise (async) | Atomic upsert: a single `INSERT ... |
where | NormQueryBuilder | Shortcut for :query():where(...). |
where_between | NormQueryBuilder | |
where_doesnt_have | NormQueryBuilder | |
where_has | NormQueryBuilder | |
where_in | NormQueryBuilder | |
where_like | NormQueryBuilder | |
where_not | NormQueryBuilder | |
where_not_in | NormQueryBuilder | |
with_count | NormQueryBuilder | |
with_trashed | NormQueryBuilder | Start a query that INCLUDES soft-deleted rows (soft-delete models only). |
wrap | NormRecord | Wrap a DB row into a persisted record (fires after_find). |
all async method
NormModel:all()
-> promise: NormRecordListPromiseResolves with every record in the table.
local users = User:all():await()
for _, u in ipairs(users) do print(u.name) endautoincrement_pk field
booleanavg async method
NormModel:avg(column: string)
-> promise: NormNumberPromiseAVG of a column across the whole table.
build method
NormModel:build(data: table<string, any>)
-> NormRecordBuild an unsaved record from a data table (nothing hits the database until you call :save()). Useful to prepare a record then persist it later.
local user = User:build({ name = "John" })
user.email = "john@x.io"
user:save():await()columns field
NormColumn[]columns_by_name field
table<string, NormColumn>count async method
NormModel:count()
-> promise: NormNumberPromiseResolves with the total row count of the table.
local total = User:count():await()create async method
NormModel:create(data: table<string, any>)
-> promise: NormRecordPromiseBuild and immediately INSERT a record. Resolves with the saved record, whose auto-increment primary key is populated.
local user = User:create({ name = "John", email = "john@x.io" }):await()
print(user.id) --> 1find async method
NormModel:find(pk: any)
-> promise: NormRecordOrNilPromiseFind a single record by its primary key. Resolves with the record or nil.
local user = User:find(1):await()
if (user) then print(user.name) endfind_by async method
NormModel:find_by(filter: table<string, any>)
-> promise: NormRecordOrNilPromiseFind the first record matching a { column = value } filter (ANDed).
local user = User:find_by({ email = "john@x.io" }):await()find_or_create async method
NormModel:find_or_create(attributes: table<string, any>, values?: table<string, any>)
-> promise: NormRecordPromiseFind the first record matching attributes; if none exists, INSERT one built from attributes merged with values. Resolves with the (existing or newly created) record. Not atomic: a unique constraint is the only true guard against a concurrent double-insert.
local player = Player:find_or_create({ account_id = id }, { name = "Guest" }):await()find_or_new async method
NormModel:find_or_new(attributes: table<string, any>, values?: table<string, any>)
-> promise: NormRecordOrNilPromiseFind the first record matching attributes; if none exists, return an unsaved record built from attributes merged with values (nothing is written until you :save() it). Resolves with the record.
local u = User:find_or_new({ email = "a@b.c" }, { name = "Anon" }):await()
if (not u.__persisted) then u:save():await() endgroup_by method
NormModel:group_by(...string)
-> NormQueryBuilderhaving method
NormModel:having(...any)
-> NormQueryBuilderhook method
NormModel:hook(event: string, fn: fun(record: NormRecord))
-> self: NormModelRegister a lifecycle hook handler. Events: before_create, after_create, before_update, after_update, before_save, after_save, before_delete, after_delete, after_find. Handlers run synchronously with the record; a before_* handler that raises cancels the operation (the promise rejects). Convenience methods exist per event (e.g. Model:before_save(fn)).
User:before_save(function(u) assert(u.name ~= nil, "name required") end)
User:after_create(function(u) print("created #" .. u.id) end)hooks field
nilevent -> { fn, ... }, created on first registration
indexes field
{ name: string, columns: string[], unique: boolean }[]?Indexes emitted at sync().
insert_many async method
NormModel:insert_many(rows: table<string, any>[], opts?: { records: boolean })
-> promise: NormNumberPromise|NormRecordListPromiseBulk-insert many rows in ONE statement. A fast path: it does NOT fire create hooks. Timestamps are stamped; auto-increment ids are left to the DB; columns missing from a row are inserted as NULL.
By default (no per-row ids available from a multi-row insert) it resolves with the affected row count. Pass { records = true } to get the inserted rows back as records WITH their ids — this uses INSERT ... RETURNING * and therefore requires a RETURNING-capable adapter (SQLite >= 3.35 / PostgreSQL / MariaDB >= 10.5); it throws otherwise.
local n = Log:insert_many({ { level = "info" }, { level = "warn" } }):await()
local recs = Log:insert_many(rows, { records = true }):await() -- recs[1].id, …join method
NormModel:join(...any)
-> NormQueryBuilderleft_join method
NormModel:left_join(...any)
-> NormQueryBuilderlimit method
NormModel:limit(...any)
-> NormQueryBuildermax async method
NormModel:max(column: string)
-> promise: NormNumberPromiseMAX of a column across the whole table.
min async method
NormModel:min(column: string)
-> promise: NormNumberPromiseMIN of a column across the whole table.
omit method
NormModel:omit(...string|string[])
-> NormQueryBuilderonly_trashed method
NormModel:only_trashed()
-> NormQueryBuilderStart a query over ONLY soft-deleted rows (soft-delete models only).
order method
NormModel:order(...any)
-> NormQueryBuilderdir:
| "ASC"
| "DESC"orm field
NormOrmpaginate async method
NormModel:paginate(page?: number, per_page?: number)
-> NormPromisePaginate the whole table. See NormQueryBuilder:paginate.
parse method
NormModel:parse(column: NormColumn, value: any)
-> anyConvert a raw driver value into a Lua value for the given column (decode).
primary_key field
string?query method
NormModel:query()
-> NormQueryBuilderStart a chainable query against this model's table.
local admins = User:query():where("admin", true):order("name"):all():await()record_class field
NormRecordA single row. Column values are plain fields (e.g. record.name).
relations field
table<string, NormRelation>scope method
NormModel:scope(name: string, fn: fun(query: NormQueryBuilder, ...any))
-> self: NormModelRegister a reusable, named query fragment. fn(query, ...) applies conditions to a query builder. The scope becomes callable as a starter (Model:active()) and chainable (query:scope("active")). The name must not collide with a built-in method.
User:scope("active", function(q) q:where("active", true) end)
User:scope("older_than", function(q, age) q:where("age", ">", age) end)
User:active():scope("older_than", 18):all():await()scopes field
nilname -> fn(query, ...), reusable query fragments
select method
NormModel:select(...string|string[])
-> NormQueryBuilderselect_raw method
NormModel:select_raw(expr: string)
-> NormQueryBuilderserialize method
NormModel:serialize(column: NormColumn, value: any)
-> anyConvert a Lua value into something storable for the given column (encode). Only json tables are transformed; a value already a string is passed through (so a pre-encoded string is never double-encoded).
soft_deletes field
string?The soft-delete column name (nil if disabled).
sum async method
NormModel:sum(column: string)
-> promise: NormNumberPromiseSUM of a column across the whole table.
local total = User:sum("coins"):await()sync async method
NormModel:sync()
-> promise: NormBooleanPromiseCreate this model's table (CREATE TABLE IF NOT EXISTS). Prefer orm:sync() to create every model at once (it also orders tables by their foreign-key dependencies). Emits this model's belongsTo foreign keys when enabled. Resolves with true.
User:sync():await()table field
stringtimestamps field
{ created: string, updated: string }?Auto-managed timestamp columns (nil if disabled).
update_or_create async method
NormModel:update_or_create(attributes: table<string, any>, values?: table<string, any>)
-> promise: NormRecordPromiseFind the first record matching attributes and UPDATE it with values; if none exists, INSERT one from attributes merged with values. Resolves with the record. (Application-level upsert; not atomic.)
local p = Player:update_or_create({ account_id = id }, { last_seen = now, name = nick }):await()upsert async method
NormModel:upsert(data: table<string, any>, opts?: { conflict: string[], update: string[] })
-> promise: NormRecordOrNilPromiseAtomic upsert: a single INSERT ... ON CONFLICT/ON DUPLICATE KEY UPDATE statement (race-safe, unlike find_or_create). data is inserted; if a row with the same opts.conflict columns exists it is updated instead. The write is one statement; the canonical row is then read back and resolved as a record.
The conflict columns MUST carry a UNIQUE (or PRIMARY KEY) constraint — that is what the database matches on. opts.conflict defaults to the primary key; opts.update defaults to every written column except the conflict columns (and created_at, preserved on an existing row).
-- create the player, or update name/last_seen if account_id already exists
local p = Player:upsert(
{ account_id = id, name = nick, last_seen = ts },
{ conflict = { "account_id" } }
):await()where method
NormModel:where(...any)
-> NormQueryBuilderShortcut for :query():where(...). Accepts (col, value), (col, op, value) or a { col = value } table.
local rich = User:where("coins", ">", 100):all():await()
local john = User:where({ name = "John" }):first():await()where_between method
NormModel:where_between(column: string, min: any, max: any)
-> NormQueryBuilderwhere_doesnt_have method
NormModel:where_doesnt_have(name: string, configure?: fun(q: NormQueryBuilder))
-> NormQueryBuilderwhere_has method
NormModel:where_has(name: string, configure?: fun(q: NormQueryBuilder))
-> NormQueryBuilderwhere_in method
NormModel:where_in(column: string, list: any[])
-> NormQueryBuilderwhere_like method
NormModel:where_like(column: string, pattern: string)
-> NormQueryBuilderwhere_not method
NormModel:where_not(column: string, value: any)
-> NormQueryBuilderwhere_not_in method
NormModel:where_not_in(column: string, list: any[])
-> NormQueryBuilderwith_count method
NormModel:with_count(...string)
-> NormQueryBuilderwith_trashed method
NormModel:with_trashed()
-> NormQueryBuilderStart a query that INCLUDES soft-deleted rows (soft-delete models only).
wrap method
NormModel:wrap(row: table<string, any>)
-> NormRecordWrap a DB row into a persisted record (fires after_find).