Skip to content

feat(server): put an installed package's models on globalThis - #2431

Merged
glennmichael123 merged 1 commit into
mainfrom
feat/package-models-globals
Sep 4, 2026
Merged

feat(server): put an installed package's models on globalThis#2431
glennmichael123 merged 1 commit into
mainfrom
feat/package-models-globals

Conversation

@glennmichael123

Copy link
Copy Markdown
Member

Completes the model half. #2427 and #2428 settled where package models do not belong, the migration generator, because a package owns its tables through the SQL it ships. This is what makes them usable: a package's actions and routes query its models by name, so they have to be in the auto-import barrel.

Precedence is spelled backwards in the two files

Both places that build the model set now include package dirs, ordered userland, then packages, then framework defaults.

The dedupe on this side is first-wins, the opposite of the migration side's last-wins, so the same intent is expressed in reverse order in the two files. An application still overrides a package's model by name, and a package still overrides a framework default. Worth knowing before editing either.

One manifest reader instead of three

The reader moved to @stacksjs/config, the one package both the migration side and the barrel can reach. core/server does not depend on @stacksjs/database, so this was about to become a third copy of the same rule, after the router's inline one. Copies of that rule eventually disagree about where a package is installed, and then routes and models describe different trees.

core/database/src/package-models.ts now delegates to it and keeps its local names, so nothing that imports it changes.

Models have no manifest key

PackageStacksMeta declares views, routes, migrations and others, but no models. Rather than add one, a package that ships models is taken to put them in app/Models, where every Stacks application does. Views keep their explicit key, which a package uses to ship more than one subtree.

Failure is not fatal

The manifest read is lazy and wrapped. It runs during boot, and a static import would pull the config graph in before the env layer has finished loading. A boot that cannot read the manifest still has the application's own models, and refusing to build the barrel would take those away too. That matters more here than elsewhere: auto-imports/index.ts uses export *, so a barrel that fails to link takes every model off globalThis at once.

Known gap, not addressed here

Discovery runs after the barrel is built. preloader.ts calls loadAutoImports() then discoverPackages(), so the first boot after installing a package builds the barrel from a manifest that does not list it, and the models appear on the second boot. Compounding it, autoImportsAreStale() is mtime-based and package managers preserve tarball mtimes, so an installed package's files are routinely older than the manifest and will not trip it. Both want a discovery-aware staleness signal, which is its own change.

Verification

  • 3 new tests, 14 in that file
  • core/config 197 pass, core/server 168 pass, core/database 880 pass / 1 fail (that failure is on clean main too)
  • name-registries.test.ts green, which the mapping flagged as the one most likely to break
  • root suite: 402 pass, 0 fail
  • framework typecheck: 4 pre-existing @stacksjs/tlsx errors
  • ./buddy lint clean apart from the pre-existing untracked storage/framework/libs/entries/

🤖 Generated with Claude Code

#2427 and #2428 settled where package models do NOT belong: the migration
generator, because a package owns its tables through the SQL it ships. This is
the half that makes them usable. A package's actions and routes query its
models by name, exactly as an application's own code does, so they have to be
in the auto-import barrel.

Both places that build the model set now include them, and the order is the
precedence: userland, then packages, then the framework defaults. Dedupe on
this side is first-wins, the opposite of the migration side's last-wins, so the
same intent is spelled backwards in the two files. An application still
overrides a package's model by name, and a package still overrides a default.

The manifest reader moved to @stacksjs/config, which is the one package both
the migration side and the barrel can reach. It was about to become a third
copy of the same resolution rule, after the router's inline one, and copies of
that rule eventually disagree about where a package is installed - at which
point routes and models describe different trees.

Models have no manifest key, so a package that ships them is taken to put them
in `app/Models`, where every Stacks application does. Views keep their explicit
key, which a package uses to ship more than one subtree.

Reading the manifest is lazy and never fatal. This runs during boot, and a
static import would pull the config graph in before the env layer has loaded; a
boot that cannot read the manifest still has the application's own models, and
refusing to build the barrel would take those away too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added database @stacksjs/datbase config @stacksjs/config storage @stacksjs/storage core labels Sep 4, 2026
@glennmichael123
glennmichael123 merged commit 5513093 into main Sep 4, 2026
10 of 11 checks passed
@glennmichael123
glennmichael123 deleted the feat/package-models-globals branch September 4, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config @stacksjs/config core database @stacksjs/datbase storage @stacksjs/storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant