Skip to content

createHostImporter's undeclared fallback resolves from @objectstack/types, not from the calling package — its documented contract says otherwise #10943

Description

@os-elon

Filed unassigned from the domain:cli lane while implementing #10908. Recording only — out of scope there, and it is a contract defect in packages/types, not in the CLI.

The claim

packages/types/src/node.ts, in createHostImporter's docblock (step 3):

Undeclared falls back to the importing package's own resolution, which is what keeps every framework-owned load working (serve's plugin-auth / service-i18n path, bootStack's service plugins).

What it actually does

The fallback is a bare import() written inside packages/types/src/node.ts. ESM resolves a bare specifier by walking up node_modules from the module that contains the call, so the fallback resolves from @objectstack/types, not from whichever package called the importer. Under a pnpm-isolated layout @objectstack/types can see only its own dependencies (@objectstack/spec).

Measured on main (worktree at eee2b65018), from a temp app root whose package.json declares nothing:

 via host importer bare import() from packages/cli
@objectstack/plugin-auth MODULE_NOT_FOUND OK
@objectstack/plugin-audit MODULE_NOT_FOUND OK
chalk MODULE_NOT_FOUND OK
@objectstack/spec OK —

@objectstack/spec is the one that resolves, and it is the only dependency packages/types declares. That is the whole pattern.

Why it matters

  1. The docblock is load-bearing and wrong. It is what a reader consults before routing a load through the importer, and it says the undeclared case keeps resolving as it did. It does not; it re-bases to @objectstack/types.
  2. It is layout-dependent, in the direction this whole line of work exists to catch. Under a hoisted npm/yarn layout the fallback usually does find the caller's dependencies, because they are flattened into one node_modules. Under pnpm isolation it does not. So "undeclared falls back" is green in some installs and absent in others — the same shape as cloud#1013 and cli: serve's dynamic cluster-driver import cannot resolve app-declared @objectstack/service-cluster* — EE multi-node OS_CLUSTER_DRIVER=redis dies at boot #10645, one level up.
  3. It is a live trap for the remaining cards on this file.Two serve.ts comments claim the capability loop loads "host copy first", but it bare-imports — the CLI's copy always wins #10909 proposes host-anchoring the capability loop, whose providers are all CLI-declared (that is the standing justification in UNRESOLVABLE_BARE_IMPORTS). Swapping import(spec.pkg) for importFromHost(spec.pkg) there would make every one of them undeclared-by-the-app and hand them to this fallback. On a pnpm-isolated install that turns a working capability into a missing one.

#10908 sidestepped it rather than fixing it: Serve.importConfigPlugin consults the declaration itself and keeps the CLI's own import() for the undeclared case, so no app loses a plugin it does not declare but the CLI ships. That is a local workaround for one call site; every other caller of createHostImporter still inherits the claim above.

Options

Recommend B with A as the immediate stopgap, because the current text will keep producing exactly the reasoning error that #10908 caught only by measuring. Wants a maintainer decision either way.

Not a duplicate

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions