Repository navigation
Description
The development target of the #runtime/v1/* subpath import in apps/api/package.json is ../../runtime/v1/dist/*. Node's resolver accepts an imports target only if it is package-relative (./…) or a bare specifier, so Node rejects this one: under --conditions=development, resolving #runtime/v1/... throws ERR_INVALID_PACKAGE_TARGET. Production uses the default target (./dist/runtime/v1/*), which is valid and unaffected. In pnpm dev the main thread does not hit the error either, because libnest dev registers @swc-node/register, whose oxc-resolver accepts the ../ target. Two places do hit it:
- Worker threads under
pnpm dev. Module hooks registered in the main thread are not inherited, soexport-worker.jsthrows when importing#runtime/v1/.... It works only because of atry/catchthat falls back to@opendatacapture/runtime-v1/.... - Vitest.
InstrumentsModule's__resolveImportcannot be exercised under the development condition.
The effect is limited to tooling and dev, but the target is invalid by spec, works only because of one resolver's leniency, and has already led to a workaround in export-worker.js.
Where
-
apps/api/package.json:72-76:"imports": { "#runtime/v1/*": { "development": "../../runtime/v1/dist/*", "default": "./dist/runtime/v1/*" } }
-
apps/api/src/instruments/instruments.module.ts:15-20:import.meta.resolve(specifier.replace(/^\/runtime/, '#runtime')). -
apps/api/src/instrument-records/export-worker.js:9-16: the fallback added for this ("we need to resolve import correctly in development").
The target has had this shape since 20de0ee1c (libnest rewrite).
Reproduce
From apps/api on Node 24.15:
node --conditions=development --input-type=module -e 'console.log(import.meta.resolve("#runtime/v1/@opendatacapture/runtime-internal/index.js"))'Actual: ERR_INVALID_PACKAGE_TARGET: Invalid "imports" target "../../runtime/v1/dist/*" defined for '#runtime/v1/*'.
Expected: file:///…/runtime/v1/dist/@opendatacapture/runtime-internal/index.js.
The same call succeeds when the swc-node hook is registered in the same thread, and fails in a Worker spawned from that thread.
Tests
apps/api/src/instruments/__tests__/instruments.module.spec.ts, test "should resolve a runtime import to the built runtime, even under the development condition", is marked it.fails. Change it to it once the target is fixed. Its comment says pnpm dev is affected; correct that, since the main thread under pnpm dev resolves the target through swc-node.
Suggested fix
Change the development target to the bare specifier @opendatacapture/runtime-v1/*. The api already depends on that package, and its exports maps ./* to ./dist/*. A scratch package using this target resolved to the same runtime/v1/dist/... URL under plain Node with --conditions=development. The try/catch fallback in export-worker.js can then be removed.
Found while raising unit test coverage in #1632.
Activity
test(api): correct which processes hit the runtime import target defect
test(api): correct which processes hit the runtime import target defect
joshunrau commentedon Oct 26, 2026
Still reproduces on
main(d86740e). Two corrections, though. The main thread passes for a different reason than the issue gives. And the suggested fix alone does not let theexport-worker.jsfallback go: without the fallback, the datahub export e2e tests fail in CI.Corrections
oxc-resolver11.20.0 also rejects the target whendevelopmentis set (Invalid "exports" target "../../runtime/v1/dist/*" defined for '#runtime/v1/*'). The main thread gets through because@swc-node/register1.12.1 gives oxc-resolver the cwdtsconfig.json, and thepathsentry atapps/api/tsconfig.json:10matches beforeimportsis read. Underdevanddev:testalike, the main thread never readspackage.jsonimports.pnpm dev:test(testing/playwright.config.ts:45), which sets nodevelopmentcondition (apps/api/package.json:11). Its workers therefore takedefault,./dist/runtime/v1/*. CI never builds the api:.github/workflows/ci.yamlhas no build step beforepnpm test:e2e, and no workspace depends on@opendatacapture/api, so no^buildreaches it. The worker's import throwsERR_MODULE_NOT_FOUND, and the fallback is what lets the exports intesting/src/specs/datahub.spec.ts:25and:174pass. Locally, a leftoverapps/api/distmakes the worker load that stale runtime copy without any error.apps/api/package.json.apps/api/Dockerfile:29writes its own./dist/runtime/v1/*mapping.In a scratch package with no
dist, the suggested{ "development": "@opendatacapture/runtime-v1/*", "default": "./dist/runtime/v1/*" }resolves from aWorkerunder--conditions=developmentand fails withERR_MODULE_NOT_FOUNDwithout it.Plan
Use one bare target with no condition:
In the same scratch setup this resolves to
runtime/v1/dist/...from aWorkerwith and without the condition. Docker brings its own mapping, sodefaultonly ever serveddev:testandpnpm startfrom a checkout, and both havenode_modules. The alternative is to keep the conditional object and addNODE_OPTIONS='--conditions=development'todev:test, which leaves two scripts that must carry the same flag.apps/api/package.json:71-76: the target above.apps/api/src/instrument-records/export-worker.js:6-16: replace theletand the try/catch with a staticimport { removeSubjectIdScope } from '#runtime/v1/@opendatacapture/runtime-internal/index.js'.apps/api/src/instrument-records/__tests__/export-worker.test.ts: delete the fallback test (:80-97),RUNTIME_INTERNAL_SPECIFIER(:15) and thevi.doUnmockinbeforeEach(:73). Nothing else uses them.apps/api/src/instruments/__tests__/instruments.module.test.ts:29-33: drop the known-defect comment and changeit.failstoit. Vitest 4.1 starts its workers with--conditions development, and its module runner handsimport.meta.resolveto Node, so this test checks the real target. The comment correction this issue asks for already landed in 52ae6f2, and the file has been.test.tssince efa9070..agents/docs/architecture/runtime-and-vendor.md:127-129and thedevelopmenttarget at.agents/docs/workspace-map.md:80-81.apps/api/AGENTS.md:274-276,.agents/docs/architecture/instrument-pipeline.md:124-125andruntime-and-vendor.md:124and:193say the mapping lives in two files. Addapps/api/Dockerfile:29as a third, paired with thedist/runtime/v1copy atapps/api/libnest.config.ts:34, because the Docker mapping now deliberately differs frompackage.json.Tests
Steps 3 and 4 cover the unit side. No new e2e test is needed, because the two export tests in
datahub.spec.tsalready run the worker underdev:test. Run them with noapps/api/dist, as in CI. A leftoverdisthides a mapping that still points at./dist.#1807 also edits both export-worker files, but not the import block. Expect at most a small conflict near
export-worker.test.ts:15.Triage note written with Claude Code. Code references point at
main@d86740e.