Skip to content

Open Service: Call remote commands in load functions#35106

Merged
JReinhold merged 8 commits into
nextfrom
split/open-service-load-body-commands
Jun 10, 2026
Merged

Open Service: Call remote commands in load functions#35106
JReinhold merged 8 commits into
nextfrom
split/open-service-load-body-commands

Conversation

@JReinhold

@JReinhold JReinhold commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Closes #

What I did

Open-service infrastructure so query load bodies invoke the channel-routed command map instead of the raw local map. This lets a peer-only command (e.g. server extractDocgen called from manager getDocgen load) run remotely instead of throwing locally. This already worked when the manager-side called the command directly, but it didn't work if the command was called from within a query's load-function.

Also makes registerService idempotent by id (needed for core beforeAll registration) and simplifies getService generics. Meaning that before we would throw when trying to register a service with an ID that already existed - now it just no-ops, allowing the same registerService-call to happen multiple times. This can silently hide bugs, but it's the practical thing to do right now.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

No user-visible behavior change until a service load invokes a peer-only command. Unit tests in service-command-transport.test.ts and service-registration.test.ts cover the new routing and idempotent registration.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Channel-routed remote command execution for service load operations; runtimes can install channel-provided command maps.
  • Bug Fixes

    • Service registration is idempotent — re-registering returns the existing instance instead of erroring.
  • Refactor

    • Simplified service retrieval API and standardized instance-type usage; added exported instance-type aliases for clearer cross-service composition.
  • Tests

    • Added coverage for remote load-body command routing and duplicate-registration behavior.
  • Documentation

    • Updated cross-service composition and server registration guidance.

Load bodies now call the channel-routed command map so queries can invoke
commands implemented only on a peer (e.g. server-side extraction). Also makes
service registration idempotent and simplifies getService generics.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold added build Internal-facing build tooling & test updates ci:normal Run our default set of CI jobs (choose this for most PRs). labels Jun 9, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold changed the title OpenService: Route load-body commands through channel for peer ops Open Service: Call remote commands in load functions Jun 9, 2026
@JReinhold JReinhold self-assigned this Jun 9, 2026
@JReinhold JReinhold requested review from AriPerkkio and ndelangen June 9, 2026 13:49
@JReinhold JReinhold added maintenance User-facing maintenance tasks qa:skip Pull Requests that do not need any QA. core and removed build Internal-facing build tooling & test updates labels Jun 9, 2026
@JReinhold JReinhold marked this pull request as ready for review June 9, 2026 13:50
JReinhold and others added 3 commits June 9, 2026 15:53
… types

getService<typeof docgenServiceDef> must resolve to ServiceInstanceOf so
runtime queries expose .loaded(). Keep a separate overload for explicit
instance types such as DocgenService.

Co-authored-by: Cursor <cursoragent@cursor.com>
…call

With getService<TInstance>, pass runtime instance types rather than typeof
the service definition. Adds DocgenService alias and updates manifests.ts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38d46587-3104-4be0-922c-7338e930fe5d

📥 Commits

Reviewing files that changed from the base of the PR and between c379225 and acd0f7d.

📒 Files selected for processing (5)
  • code/core/src/core-server/change-detection/change-detection-service.ts
  • code/core/src/core-server/change-detection/change-detection.test-helpers.ts
  • code/core/src/shared/open-service/service-runtime.ts
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/shared/open-service/services/module-graph/definition.ts
✅ Files skipped from review due to trivial changes (3)
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/core-server/change-detection/change-detection.test-helpers.ts
  • code/core/src/core-server/change-detection/change-detection-service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/shared/open-service/service-runtime.ts

📝 Walkthrough

Walkthrough

This PR adds channel-installed command maps for load bodies (attachChannelCommands), routes remote commands via the channel while preserving local gating, consolidates getService to an instance-type generic, makes registerService idempotent by service id, and adds tests covering load-body remote-command routing.

Changes

Channel-routed load-body command execution with service-instance typing

Layer / File(s) Summary
Service instance-type API pattern
code/core/src/shared/open-service/types.ts, code/core/src/shared/open-service/fixtures.ts, code/core/src/shared/open-service/service-registry.ts, code/core/src/shared/open-service/README.md, code/core/src/shared/open-service/server.test-d.ts, code/core/src/shared/open-service/server.test.ts, code/core/src/shared/open-service/service-registration.test.ts, code/core/src/core-server/utils/manifests/manifests.ts, code/core/src/shared/open-service/services/docgen/definition.ts
Introduce exported instance-type aliases (e.g., MutableRecordLookupService, DocgenService), update docs/tests to use instance-type generics for ctx.getService(...), remove getService overloads and consolidate to getService<TInstance = RuntimeService>(serviceId): TInstance.
Service registration idempotency
code/core/src/shared/open-service/service-registry.ts, code/core/src/shared/open-service/service-registration.test.ts, code/core/src/shared/open-service/README.md
registerService now returns the existing runtime when re-registering the same service id; duplicate-registration error import/throw path removed and tests/docs updated to assert idempotency.
Runtime load-body command routing infrastructure
code/core/src/shared/open-service/service-runtime.ts, code/core/src/shared/open-service/types.ts
Add attachChannelCommands to ServiceRuntime, make loadCommands mutable, expose getLoadCommands() to loads, and update buildGatedCommands/runLoadBody to route remote commands via the installed map while preserving local gated wrappers.
Channel connection and command map installation
code/core/src/shared/open-service/service-transport.ts, code/core/src/shared/open-service/service-registry.ts
connectServiceToChannel now accepts a runtime parameter and installs the channel-routed command map onto the runtime using runtime.attachChannelCommands(...) after creating the transport.
Load-body remote command routing test coverage
code/core/src/shared/open-service/service-command-transport.test.ts
Add fixtures and tests that validate load-body behavior when invoking local commands (no SERVICE_COMMAND_INVOKE) and when invoking peer-only commands (emits SERVICE_COMMAND_INVOKE and consumes SERVICE_COMMAND_RESULT, resolving load to null).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • storybookjs/storybook#35068: Implements the remote command protocol and channel transport that this PR uses to route load-body commands.
  • storybookjs/storybook#34875: Introduces the server-side registerService/getService flow that this PR refactors for idempotency and instance-type generics.
  • storybookjs/storybook#34960: Touches the open-service registration/getService surface and is related to changes in how registration and exported server utilities are handled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread code/core/src/shared/open-service/service-command-transport.test.ts Outdated
Comment thread code/core/src/shared/open-service/service-runtime.ts Outdated
Use onTestFinished for handler spy restore in load-body test, and reuse a
single remoteCommandNames Set cleared on attachChannelCommands.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold requested review from a team June 10, 2026 08:47
@JReinhold JReinhold enabled auto-merge June 10, 2026 08:51
…ce type

After merging next, the getService<typeof def> callsites for core/module-graph
resolved to the definition type (no subscribe/loaded) under this PR's single
getService<TInstance> generic. Add a ModuleGraphService instance-type alias and
use it. Also fix remoteCommandNames Set.add spread misuse.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold merged commit dda0188 into next Jun 10, 2026
134 of 138 checks passed
@JReinhold JReinhold deleted the split/open-service-load-body-commands branch June 10, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal Run our default set of CI jobs (choose this for most PRs). core maintenance User-facing maintenance tasks qa:skip Pull Requests that do not need any QA.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants