LearnNewsExamplesServices
Frontmatter
titlefix(data): autoLoad awaits remotes-api registration (#12132)
authorneo-opus-ada
stateMerged
createdAtMay 28, 2026, 3:22 PM
updatedAtMay 28, 2026, 3:33 PM
closedAtMay 28, 2026, 3:33 PM
mergedAtMay 28, 2026, 3:33 PM
branchesdevfeature/autoload-remotes-registration
urlhttps://github.com/neomjs/neo/pull/12134
Merged
neo-opus-ada
neo-opus-ada commented on May 28, 2026, 3:22 PM

Authored by Claude Opus 4.7 (Claude Code) as @neo-opus-ada. Session provenance: lane-claim A2A MESSAGE:894591f6 (#12132).

FAIR-band: operator-directed pickup — the operator explicitly assigned #12132 (and #12133) this session, superseding the standing "no new items until open team PRs merge" hold. Author-lane pickup discipline is N/A for directed work.

Resolves #12132

A Store with autoLoad: true + a websocket api lost a boot race: Store.onConstructed() fires load() one microtask after construction, but the remotes-api stubs register behind an async dynamic import + fetch, so load() found no stub and bailed at console.error('Api is not defined') — no RPC, no load.

The fix reuses the existing core.Base initAsync()/ready() lifecycle rather than a hand-rolled promise:

  • Neo.remotes.Api awaits load() inside initAsync(), so the singleton only reaches isReady once the api JSON is fetched and the service stubs are registered.
  • Store.load()'s api branch awaits Neo.remotes.Api.ready() when the stub is missing (gated on Neo.config.remotesApiUrl) before giving up, then re-resolves the stub.
  • src/worker/App.mjs now just imports the module (creation triggers initAsyncload); the explicit .then(module.default.load()) is removed to avoid a double-load.

url/static autoLoad stores and apps without remotesApiUrl are unaffected; no boot latency added for non-remote apps.

Evidence: L1 (53/53 test/playwright/unit/data/ regression green; rides the core/Base initAsync/ready() primitive) → L3 required (api-store autoLoads on boot — observable runtime effect across the App↔Data worker boundary the single-thread unit sim cannot model). Residual: runtime AC verified via integration with a real autoLoad:true + websocket-api store. [#12132]

Deltas from ticket

  • The ticket proposed a "registration-ready signal on Neo.remotes.Api"; implemented via the existing initAsync/ready() primitive instead of a bespoke promise — cleaner, less new surface.
  • Incidental: removed pre-existing trailing whitespace flagged by the check-shorthand pre-commit hook in the two touched files (App.mjs duplicated JSDoc blocks, Api.mjs). Mechanical, no logic change.

Test Evidence

  • npm run test-unit -- test/playwright/unit/data/53 passed (no regression from the Store.load change).
  • node --check clean on all three changed files.
  • A dedicated boot-race unit test is impractical: the api load path bottoms out in a cross-worker RPC (promiseMessage('data', {action:'rpc'})) the single-thread unit sim does not model, and the remotes Api singleton is created at import-time (mocks cannot be injected before initAsync). The fix rides the already-covered initAsync/ready() primitive.

Post-Merge Validation

  • In a live app, a Store with autoLoad: true + websocket api: {read} populates on boot with no controller-side workaround.
  • No "Api is not defined" console error for a correctly-wired remote api store on boot.

Cross-family review

Per §6.1 this needs a cross-family Approved review before merge; will route to a primary reviewer once CI is green.