-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
LibCodex-1.0 is a static-catalog library with a hybrid load model. This page covers the design decisions that shape what's possible at the API layer.
Every catalog assembles itself from three sources, in priority order:
-
Bundled seed. Each module ships a pre-baked
Data/<Module>.luasnapshot generated by the bake tool from offline DBC dumps and Wowhead scrapes. The seed is the floor: every consumer gets it for free. -
SavedVariables.
LibCodexDBis restored atPLAYER_LOGIN. It carries everything the user has discovered across previous sessions — useful for fields the bundle doesn't know (level-scaling labels, flavor-specific name overrides, NPC titles seen in chat, etc.). -
Runtime adapters. Functions registered via
:RegisterAdapter(name, fn)run once atPLAYER_LOGINand may also hook events for continuous capture. Adapters push entries into modules viaAdd.
All three sources merge into the same per-module collection. Provenance is tagged on each entry's sources array ("bundled", "savedvars", custom adapter names).
LibCodex ships as a small core (LibCodex-1.0) plus 73 sibling LoadOnDemand sub-addons, one per data module. The naming convention is LibCodex-1.0-<ModuleName> (case-preserved). Examples:
| Module | LoD sub-addon |
|---|---|
NPCs |
LibCodex-1.0-NPCs |
Quests |
LibCodex-1.0-Quests |
ChatChannels |
LibCodex-1.0-ChatChannels |
The core only carries the factory, the registration plumbing, and the typed accessors. Per-module bundle data lives in the LoD addon's Data/<Module>.lua and only loads when something actually queries that module.
Auto-load on first miss. When a consumer calls :Get(id) on a module that has no data, the collection's :Get calls LibCodex:_TryLoadModule(self._name) exactly once. If the LoD sub-addon is installed, it loads, ingests its bundled data via _FeedBundledRowsLazy, and the lookup retries. If the LoD sub-addon is missing or disabled, the lookup falls through to whatever the user's SavedVariables and runtime adapters provided.
Why per-module split. Three reasons:
- Memory. Most addons only touch a handful of modules. An NPC-tooltip addon has no reason to pay for the Spells module's hundreds of thousands of rows.
- Bytecode constant pools. Lua 5.4 caps function-prototype constant pools. A monolithic library with every module's data inlined hits that ceiling. Per-module addons keep each Proto small.
- Independent updates. Re-baking one module doesn't force a re-download of the whole library on CurseForge.
Bundled data files use LibCodex:_FeedBundledRowsLazy(module, columnsCSV, thunk) rather than emitting the table inline. The thunk is a zero-arg function whose body returns the rows table. Lua only constructs the table when the thunk is called.
This matters because the dominant memory cost of bundled data isn't the string interning (Lua interns string constants at parse time regardless), it's the per-table headers and hash-part allocations for each row. Wrapping in a thunk defers that cost until the consumer actually queries the module.
:Get(id) materializes only the requested row. :Search(query, opts) walks the lazy index without materializing non-matching rows. :ExpandAll() is the explicit override when a consumer needs every row in dict form.
Three formats land bundled data into a collection:
-
_FeedBundled(module, entries)— table of fully-formed entry dicts. Used for small enums. -
_FeedBundledRows(module, columnsCSV, rows)— positional row arrays plus a column header. Cheaper than dict form. -
_FeedBundledRowsLazy(module, columnsCSV, thunk)— same as_FeedBundledRowsbut the rows table is wrapped in a thunk for lazy materialization. The default for catalogs. -
_FeedBundledTSV(module, columnsCSV, blob)— legacy tab-separated string blob with byte-offset indexing. Back-compat path; new modules use the row format.
All four are no-ops if the receiving module hasn't registered yet — the data is stashed in pendingHydrate / pendingRows / pendingLazyRows / pendingTSV and drained on :RegisterModule(name, collection).
Adapters are functions of the shape function(LC) ... end. Registered via LibCodex:RegisterAdapter(name, fn) and called once at PLAYER_LOGIN by :RunAdapters(). The library wraps each call in pcall so a misbehaving adapter doesn't break the load chain.
Adapters can also register their own event hooks for continuous data capture. The library's only job is the initial fan-out; ongoing observation is each adapter's responsibility.
The runtime adapter that ships with the library lives at Adapters/Runtime.lua and feeds basic player-context data (realm, faction, race, class) at login. Consumer addons can register additional adapters from their own code.
Schema:
LibCodexDB = {
version = 1,
modules = {
NPCs = { [id] = entry, ... },
Items = { [id] = entry, ... },
Quests = { [id] = entry, ... },
...
},
}
The library hooks PLAYER_LOGIN to restore and PLAYER_LOGOUT to persist. Persistence iterates every registered module and writes its :AllRaw() map. Modules that haven't been queried this session and are still in lazy-chunk form get materialized at logout — this is intentional, so the SV file always reflects the full known catalog.
Every entry has a sources array tagging where each value came from. The merge rules in mergeEntry (in Modules/Common.lua) prevent runtime data from clobbering hand-curated bundled data:
-
_handcrafted = true— every field on the entry is locked. Incoming data merges only into fields the existing entry doesn't have. -
_locked = { "field1", "field2" }— only the listed fields are locked. -
_overwrite = { fieldName = true }— incoming entry forces an override on a specific field. Ignored for fields covered by_handcrafted/_locked.
This is what lets the bake tool re-emit catalogs without stomping on manual corrections committed in Data/<Module>.lua.
The library uses LibStub for major/minor versioning. LIB_MAJOR is the literal string "LibCodex-1.0". LIB_MINOR is a sequential build number that increments by 1 on every release pass. LibStub keeps whichever copy registered with the highest minor — so an addon embedding an older copy automatically defers to a more recently-released one loaded by another consumer.
The standalone install registers /codex for diagnostics. The legacy in-game GUI dashboard moved to a separate consumer addon (Forge_Codex); /codex gui redirects to it. The slash command primarily exposes the log window now.
LibCodex-1.0 © ChronicTinkerer · MIT License · Report an issue