Jump to content

ICT:Documentation:Lua Numbering Module AssetID

From Costa Sano MediaWiki

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.