ICT:Documentation:Lua Numbering Module AssetID
Explanation of the Lua Numbering Module (Module:AssetID)
This page documents the purpose, design, and inner logic of the Lua module Module:AssetID used in Asset v2.
It is intended as long-term reference documentation and is written to be understandable years later without re-reading the original code discussion.
Purpose of This Document
Lua modules in MediaWiki often appear opaque because:
- they run server-side
- they interact with Cargo
- they return data in non-obvious formats (JSON)
- they are usually called indirectly (via JavaScript)
This document explains why the module exists, what it does, and why it is intentionally limited.
Role of the Lua Module in Asset v2
The Lua module has exactly one responsibility:
Compute the next Asset identifier and sequence number.
It does NOT:
- create pages
- reserve identifiers
- write to Cargo
- upload or rename files
- enforce immutability
- validate user permissions
The module is purely computational.
Why Lua Is Used at All
Lua is used instead of JavaScript or PHP because:
- it runs server-side
- it can safely query Cargo
- it is deterministic
- it does not depend on the browser
- it does not require a custom extension
Lua is the correct layer for “read-only system logic”.
Where the Module Lives
The module is defined at:
Module:AssetID
It is a standard Scribunto Lua module.
Full Module Code
For reference, the module code is:
local p = {}
local cargo = mw.ext.cargo
local function pad(n)
return string.format("%04d", tonumber(n))
end
function p.generate(frame)
local a = frame.args
local chapter = a.Chapter or ""
local place = a.Place or ""
local org = a.Organisation or ""
if (place ~= "" and org ~= "") then
return mw.text.jsonEncode({ error = "Choose Place OR Organisation." })
end
if (place == "" and org == "") then
return mw.text.jsonEncode({ error = "Place or Organisation required." })
end
local ctxField = place ~= "" and "Place" or "Organisation"
local ctxValue = place ~= "" and place or org
local res = cargo.query(
"Assets",
"MAX(Sequence)=max",
{
where = string.format(
"Chapter='%s' AND %s='%s'",
chapter, ctxField, ctxValue
)
}
)
local nextSeq = (res[1] and res[1].max or 0) + 1
local code = string.format(
"%s-%s-%s",
mw.title.new(chapter).text,
mw.title.new(ctxValue).text,
pad(nextSeq)
)
return mw.text.jsonEncode({
identifier = code,
sequence = nextSeq
})
end
return p
Each part is explained below.
Module Structure
local p = {}
return p
What this does:
- Defines a module table
- Exposes functions via that table
This is standard Scribunto structure.
Access to Cargo
local cargo = mw.ext.cargo
What this does:
- Imports the Cargo Lua interface
- Allows querying Cargo tables
Important: The module uses Cargo only for reading, never for writing.
Padding Function
local function pad(n)
return string.format("%04d", tonumber(n))
end
What it does:
- Converts a number into a 4-digit string
- Adds leading zeros if necessary
Examples:
- 1 → 0001
- 27 → 0027
- 305 → 0305
Why this exists:
- Enforces fixed-width identifiers
- Keeps identifiers sortable and readable
Entry Point: generate()
function p.generate(frame)
This is the only public function.
It is called by JavaScript using Scribunto.
Reading Input Arguments
local a = frame.args local chapter = a.Chapter or "" local place = a.Place or "" local org = a.Organisation or ""
What this does:
- Reads values passed from JavaScript
- Defaults missing values to empty strings
Why this matters:
- Prevents nil errors
- Allows explicit validation
Mutual Exclusivity Check
if (place ~= "" and org ~= "") then
return mw.text.jsonEncode({ error = "Choose Place OR Organisation." })
end
What this does:
- Rejects invalid input
- Returns a JSON error message
Important: The module does NOT throw errors. It returns structured information to the caller.
Required Context Check
if (place == "" and org == "") then
return mw.text.jsonEncode({ error = "Place or Organisation required." })
end
What this does:
- Enforces the rule that exactly one context must exist
Again: The module does not decide what happens next. It only reports the problem.
Determining the Context Field
local ctxField = place ~= "" and "Place" or "Organisation" local ctxValue = place ~= "" and place or org
What this does:
- Determines which Cargo field to query
- Keeps logic compact and explicit
This is a standard Lua idiom.
Cargo Query for the Maximum Sequence
local res = cargo.query(
"Assets",
"MAX(Sequence)=max",
{
where = string.format(
"Chapter='%s' AND %s='%s'",
chapter, ctxField, ctxValue
)
}
)
What this does:
- Queries the Assets table
- Finds the highest existing Sequence number
- Restricts by Chapter and Context
Why MAX() is used:
- Fast
- Deterministic
- Does not require locks
- Accepts gaps naturally
Why Gaps Are Accepted
The module intentionally allows gaps because:
- Asset creation may fail after number generation
- Users may abandon forms
- Concurrent users may collide
Trying to prevent gaps would require:
- locks
- reservations
- cleanup logic
This is explicitly avoided.
Gaps are acceptable in archival identifiers.
Computing the Next Sequence
local nextSeq = (res[1] and res[1].max or 0) + 1
What this does:
- If records exist, increment the max
- If none exist, start at 1
This line handles both cases safely.
Building the Identifier
local code = string.format(
"%s-%s-%s",
mw.title.new(chapter).text,
mw.title.new(ctxValue).text,
pad(nextSeq)
)
What this does:
- Extracts page titles
- Builds the final identifier string
Assumption: Page title equals the Code of Chapter / Place / Organisation.
This is a documented system invariant.
Returning Data as JSON
return mw.text.jsonEncode({
identifier = code,
sequence = nextSeq
})
What this does:
- Returns structured data
- Allows JavaScript to parse it reliably
Why JSON is used:
- Safe transport format
- Extensible
- Error and success responses use same mechanism
Important Design Limits
This module intentionally does NOT:
- reserve identifiers
- check for page existence
- detect concurrent conflicts
- write anything to Cargo
- modify pages or files
All of that is handled elsewhere or not at all.
Mental Model to Keep
You can safely remember:
- Lua module = calculator
- Input = Chapter + Context
- Output = identifier + sequence
- No side effects
- No state
Status
This document is stable reference documentation.
It exists to support long-term understanding and memory retention.