Repository navigation
Package manager devx #1
Description
I'm designing the developer experience for LyraCore's package system and want feedback before building the tooling. This proposal covers what packages are, the developer story for modding, and how you'd install, enable, and ship packages including an official package collection you can install by name.
Where packages stand today
A package is a folder dropped into packages/ that extends the server with zero edits to core files. The mechanism already works in small tests: simple playerbots and the Deadmines encounters run as packages today. What's missing is tooling: installing one currently means manually cloning a folder into the right place, knowing to republish every database, and knowing an unrelated importer flag repacks the client patch.
One piece of information up front: SpacetimeDB runs exactly one WASM module per database, so there's no runtime plugin loading. Server-side package code compiles into the module. That sounds heavyweight, but publishing is an auto-migrating hot-reload that takes seconds, so "recompile and republish" is closer to restarting a script than to a new deployment.
Anatomy of a package
A package is one folder carrying any mix of four kinds of content — server code, client UI, and (roadmap) datascripts and runtime scripts:
packages/<name>/
src/mod.rs # server-side Rust, compiled into the module
src/*.rs
client/
addons/<Name>/ # WoW addons, installed into Interface/AddOns/
mpq/<path> # FrameXML overrides, packed into the client patch MPQ
data/*.ts # ROADMAP: datascripts — typed code that changes game data
scripts/*.ts # ROADMAP: runtime scripts — hot-loadable behavior
Any subset is legal. A client-only package (just addons/) is fine, so is server-only.
Server code (src/)
The build scans packages/*/src/ and compiles what it finds into the module alongside core code so a package defines tables, reducers, and RLS filters exactly the way core does. Beyond that, packages hook into core through marker macros colocated with your code. The point of the design is that a package gets as close to patching core as you can get without forking it: your code compiles into the same module, registers into the same dispatch points, and runs with the same access as core code. Three extension points exist today, and the set grows as modders need more
game_hook!(<event>, fn ...)— react to a gameplay event at its core chokepoint. The current catalog is 17 events:on_login,on_logout,on_levelup,on_kill,on_death,on_damage_taken,on_cast_resolved,on_loot,on_quest_accept,on_quest_turnin,on_gossip_select,on_aggro,on_creature_spawn,on_creature_death,on_hp_threshold,on_go_used,on_group_invite.
Hooks are notify-only for now. You observe and act through the same functions core uses (deal damage, grant items/XP, spawn creatures, send chat); hooks that mutate the triggering action (scale incoming damage, veto a cast) are a separate, later design.
game_tick_pass!(fn ...)— run periodic logic on the server tick (every 0.5s, after all core passes; self-throttle if you want a slower cadence). This is what playerbots' AI runs on.character_owned!(...)— required bookkeeping for any table keyed by a character: it enrolls your rows in character deletion and cross-shard transfer.
Everything is discovered at build time from the folder with no registration file to edit, no diff to maintain.
Client UI (client/)
A package can ship regular WoW addons, which work exactly like any addon you've ever installed. Several packages can each bring their own and nothing conflicts. It can also override stock UI files (mpq/), which is sometimes necessary. The login screen and character select can't be touched by addons, for example.
Scripts (roadmap)
TypeScript for extensions, Rust for systems. Rust is for packages that define tables and compiled game logic. Everything lighter-weight is TypeScript:
data/*.ts — datascripts. Changing game data: a class's talents, a vendor's inventory, a new spell cloned from an old one should be typed code, not SQL dumps or config rows. This is the [tswow](https://github.com/tswow/tswow) datascripts lesson: most modding is data, and data modding is joyful when your editor autocompletes every field of every spell and flags typos before anything runs:
// data/spells.ts
const nova = data.spell(133).clone(90001) // start from Fireball
nova.name = "Fire Nova"
nova.effects[0].base_points = 50
for (let rank = 1; rank <= 5; rank++) { // it's code — loop, derive, compose
const r = nova.clone(90000 + rank)
r.effects[0].base_points = 40 + rank * 12
}The types aren't hand-maintained, but generated from the server's actual table schema by SpacetimeDB's own codegen (spacetime generate), so they can never drift from reality, and a core schema change that breaks a package's datascript fails typecheck in CI before any server hits it.
You run your datascripts when you build the package (with bun or deno?), and the package ships the rows they produced. Installing a package is just applying those rows already generated. A datascript only touches the fields it actually sets, so when the base game data gets updated, your changes carry over instead of being wiped out. And if two packages try to set the same field on the same row, installing fails with an error naming both packages, nothing gets silently overwritten.
scripts/*.ts — runtime scripts. Lighter-weight behavior: reacting to the hook events above, custom ability logic, that kind of thing. You write TypeScript against a typed API; at build time it's transpiled to Lua, which runs inside the server module on an embedded Lua VM. Script changes deploy without rebuilding anything.
Scripts don't get raw database access, they act through a curated API (deal damage, grant items, spawn creatures, send chat), and every script run has a hard execution budget, so a buggy script errors out instead of hanging the server. Hand-written Lua works too. TypeScript is the front door, but the runtime executes Lua so Eluna experience carries over directly.
Client addons stay hand-written Lua for now — the 1.12 client speaks Lua 5.0, older than what the TS-to-Lua toolchain targets, so "TypeScript for addons" is a possible later step.
Both roadmap tiers install, enable, and publish through exactly the commands below — which is why we want the devx right first.
The proposal
Installing a package becomes three commands:
lyracore packages add dungeons # fetch + install the package
lyracore publish # push to every database
lyracore client sync # update your client's patch + addons
Each command tells you what to run next.
An official packages repository
I'd create LyraCoreProject/packages: one repository where each top-level directory is an installable package. Optional first-party content lives there instead of inside core: playerbots, and dungeon encounter scripts (a dungeons package). If you're running a fully custom server and don't want stock encounter code, you just don't install it. Core ships leaner, and what you do run is an explicit choice.
lyracore packages add <x> accepts three shapes, no flags:
- a bare name (
dungeons) — looked up in the official repository - a git URL — any third-party package repo
- a local path — your own work-in-progress package or downloaded package
Before anything is installed, the CLI shows you what the package contains. It shows how many tables, reducers, and hooks it registers, and whether it ships client addons or whole-file FrameXML overrides, before asking for confirmation. Compiled package code is trusted code (it runs with full access, like a patch to the server itself), so the tooling's job is to make reviewing easy, not to act as a sandbox.
The full command set
lyracore packages list # what's installed, from where, what each ships
lyracore packages add <name|url|path>
lyracore packages update [<name>] # pull the latest source
lyracore packages enable <name>
lyracore packages disable <name>
lyracore packages remove <name>
lyracore packages new <name> # scaffold a package from the reference example
A package is enabled if and only if its folder sits in packages/ without any manifest or config file to drift out of sync. disable parks the folder in a "disabled" subfolder. The CLI narrates exactly what moved and what happens on the next publish including warning you before publish that removing a package whose tables still hold data is refused by the safe-migration check.
client sync
Packages can ship client-side UI, which means your WoW client's patch MPQ and addons need to match your server. Today that's buried inside the importer. Here's the new proposal:
lyracore client sync # rebuild the patch + addons, install into your client
lyracore client pack --out <dir> # build the distributable patch you hand to your players
sync also tells you when a package you've disabled still has its addon installed from a previous pack.
Feedback
Feel free to post feedback on this in the discord. I'm looking for feedback on everything but would appreciate some thoughts on these specific topics:
- The language split. TypeScript for datascripts and runtime scripts, Rust for systems, generated types from the live schema. Does this match how you'd want to mod, or would you rather write Lua directly end-to-end? (Hand-written Lua stays supported for runtime scripts either way.)
- Anything in your modding workflow this three-command flow doesn't cover? Especially from folks who've run tswow modules or Eluna scripts since that's the what I'm stealing from.
- Naming.
packagesas the noun,add/enable/disableas the verbs: any strong objections before the decision is made?
Activity
Mathih13 commentedon Aug 11, 2026
The implementation track for this proposal migrated from the private tracker on 2026-08-11: #90 #91 #92 #93 #94 #95 #96 #97 #98 #99 #100. The full design doc lives in the private working origin; this issue remains the public proposal/discussion surface.
Mathih13 commentedon Aug 13, 2026
Triage Notes
Labeled
enhancement/P2as the tracking proposal, not an implementable ticket. It stays open as the public feedback thread while the decomposition proceeds.Implementation lives in the sub-issues that cite this proposal:
packagesverbs), packages devx: lyracore publish defaults to ALL provisioned databases #91 (publish defaults), Confirm lyracore client sync installs a working patch and addons on a real 1.12.1 client #92 (client sync/client pack)Related operator-surface work: #35, #58.
No agent should pick this issue up directly — take a sub-issue.
Mathih13 commentedon Aug 24, 2026
Human Brief
Category: enhancement
Summary: Keep this issue as the public Package-system proposal and coordinate its formal sub-issues.
Why this needs a human:
This is a product and sequencing tracker, not one implementation task. It includes organization repository creation, private Package extraction, legal decisions about client distribution, and real-client verification.
Current behavior:
Core can compile Package Rust and pack Package client content. The CLI lifecycle, Package Deltas, Datascripts, Runtime Scripts, and official collection are incomplete or absent.
Desired behavior:
Use the formal sub-issue tree as the implementation contract. Agent-ready children may land independently when their dependencies are satisfied. Human-owned children retain external access, data-risk, legal, or live-client decisions. Keep this issue open for public feedback and close it only when the intended Package development path is coherent end to end.
Acceptance criteria:
Out of scope:
Mathih13 commentedon Oct 8, 2026
Closing the proposal: 13 of its 15 parts have shipped (the packages verbs, lyracore publish, the Official Package Collection, Package Deltas, Datascripts and Runtime Scripts). #92 and #304 track the two real-client checks that remain.