Skip to content

improvement(client): improved internal submitMessage, reSubmit, rollback, and submitSignal type-safety#24319

Merged
jason-ha merged 14 commits intomainfrom
test/client/ops/internal-runtime-type-tightening
Oct 31, 2025
Merged

improvement(client): improved internal submitMessage, reSubmit, rollback, and submitSignal type-safety#24319
jason-ha merged 14 commits intomainfrom
test/client/ops/internal-runtime-type-tightening

Conversation

@jason-ha
Copy link
Contributor

@jason-ha jason-ha commented Apr 10, 2025

improvement(client): improved internal submitMessage, reSubmit, rollback, and submitSignal type-safety

In Legacy API:

  • Add type parameter to IEnvelope for contents to support stricter typing where desired.
  • Remove unused parameters from mock submitMessage and submitSignal methods.
  • Clean up some mock types.

Internally:

  • Replace any in ISignalEnvelope with unknown.
  • Relocate FluidDataStoreMessage to runtime-definitions. Eventually this can serve as basis for message types for DataStores.
  • In datastore, create LocalFluidDataStoreRuntimeMessage representing the two known messages for DataStores.
  • FluidDataStoreContext requires OutboundFluidDataStoreMessage for stricter reSubmit and rollback methods.
  • Create IFluidRootParentContextPrivate for the transition from DataStore to ContainerRuntime and use explicit types in ContainerRuntime and ChannelCollection.
  • In ChannelCollection restore some old method names with strict typing that internal callers must conform to. The IFluidDataStoreChannel portions that were not required by current use were refactored to new ComposableChannelCollection class.
  • Replace wrapContext with formParentContext that handles both IFluidParentContextPrivate and IFluidRootParentContextPrivate by requiring the specific submitMessage and submitSignal functions
  • Within ContainerRuntime:
    • Update submitMessage to expect specific messages
    • Update submitSignal to expect new AddressedUnsequencedSignalEnvelope that is specific subset of ISignalEnvelope and is exactly IEnvelope<ISignalEnvelope["contents"]>
  • Fixup tests to use more realistic data (as required by stricter typing).

Potential Future Improvements:

  • Change {@link IFluidDataStoreChannel} and {@link IFluidParentContext}, to have a generic specifying T extends FluidDataStoreMessage and uses T["type"] and T["content"] to qualify message related methods, preferably where submitMessage, reSubmit, and rollback have overloads to ensure callers pair values correctly.
  • A further improvement would be to reshape submitMessage, reSubmit, and rollback to accept T as message parameter instead of type and content parameters that are hard to convince TypeScript must be paired in implementations.

…ack, and submitSignal type-safety

In Legacy API:
- Add type parameter to `IEnvelope` for `contents` to support stricter typing where desired.
- Deprecate `FluidDataStoreRuntime.submitMessage` as not required per `IFluidDataStoreChannel`. `IFluidParentContext` (which is base interface for `IFluidDataStoreContext`) should always be used to access `submitMessage` functionality.
- Remove unused parameters from mock `submitMessage` and `submitSignal` methods.

Internally:
- Replace `any` in `ISignalEnvelope` with `unknown`.
- Relocate `FluidDataStoreMessage` to runtime-definitions. Eventually this can serve as basis for message types for DataStores.
- In `datastore`, create `LocalFluidDataStoreRuntimeMessage` representing the two known messages for DataStores.
- `FluidDataStoreContext` requires `OutboundFluidDataStoreMessage` for stricter `reSubmit` and `rollback` methods.
- Create `IFluidRootParentContext` for the transition from DataStore to `ContainerRuntime` and use explicit types in `ContainerRuntime` and `ChannelCollection`.
- In `ChannelCollection` restore some old method names with strict typing that internal callers must conform to. The `IFluidDataStoreChannel` portions that were not required by current use were refactored to new `ComposableChannelCollection` class.
- Replace `wrapContext` with `formParentContext` that handles both `IFluidParentContext` and `IFluidRootParentContext` by requiring the specific `submitMessage` and `submitSignal` functions
- Within `ContainerRuntime`:
  - Update `submitMessage` to expect specific messages
  - Update `submitSignal` to expect new `AddressedSignalEnvelope` that is specific subset of `ISignalEnvelope` and is exactly `IEnvelope<ISignalEnvelope["contents"]>`
- Fixup tests to use more realistic data (as required by stricter typing).

Potential Future Improvements:
- Change {@link IFluidDataStoreChannel} and {@link IFluidParentContext}, to have a generic specifying `T extends FluidDataStoreMessage` and uses `T["type"]` and `T["content"]` to qualify message related methods, preferably where `submitMessage`, `reSubmit`, and `rollback` have overloads to ensure callers pair values correctly.
- A further improvement would be to reshape `submitMessage`, `reSubmit`, and `rollback` to accept `T` as `message` parameter instead of `type` and `content` parameters that are hard to convince TypeScript must be paired in implementations.
Copilot AI review requested due to automatic review settings April 10, 2025 21:05
@jason-ha jason-ha requested review from a team as code owners April 10, 2025 21:05
@jason-ha jason-ha requested review from markfields and vladsud and removed request for Copilot April 10, 2025 21:05
@github-actions github-actions bot added area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: runtime Runtime related issues area: tests Tests to add, test infrastructure improvements, etc changeset-present public api change Changes to a public API base: main PRs targeted against main branch labels Apr 10, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 26 out of 27 changed files in this pull request and generated no comments.

Files not reviewed (1)
  • packages/runtime/datastore/package.json: Language not supported
Comments suppressed due to low confidence (2)

packages/framework/attributor/src/runtimeAttributorDataStoreChannel.ts:189

  • The reSubmit method in this attributor channel now has no parameters, which deviates from the rest of the interface. Consider adding documentation or a comment clarifying that reSubmit is intentionally not supported in this context.
public reSubmit(): void {

packages/runtime/container-runtime/src/channelCollection.ts:724

  • [nitpick] The naming of 'reSubmitContainerMessage' (and the corresponding 'rollbackDataStoreOp') differs from the standard naming conventions used in other parts of the code. Consider standardizing the naming across modules for easier maintainability.
public readonly reSubmitContainerMessage = (

Copy link
Contributor

@alexvy86 alexvy86 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked at the actual change in detail, but a few doc comments in the meantime

…jected types

for `submitSignal` and `submitMessage`.

Additionally, deprecate `MockFluidDataStoreRuntime.submitMessage`.
* @internal
*
* @privateRemarks
* Future use opportunity:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds nice!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'm talking out of both sides of my mouth - in the other comment I talked down adding these generics, didn't immediately connect the dots to this comment til now. I totally get the structure here and it's obviously appealing in one sense, but I'm concerned about how it will feel in practice. Maybe too many generics? Other downsides I'm not thinking of?

readonly submitSignal: (envelope: AddressedSignalEnvelope, targetClientId?: string) => void;
}

type SubmitKeys = "submitMessage" | "submitSignal";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Either export as internal and use in IFluidRootParentContext or inline these in both places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. There is a separation of concerns here that happens to coincide because of this utility and makes it look like they must be the same. In practice there can be other differences between the two interfaces.
I'll want to look at what is going on with IFluidParentContextPrivate. Might be some changes per that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IFluidParentContextPrivate doesn't appear to be doing much. It is just making two properties required by respecifying them (currently).

Sticking with this separation for now.

export function formParentContext<T extends IFluidParentContext | IFluidRootParentContext>(
context: Omit<IFluidParentContext & IFluidRootParentContext, SubmitKeys>,
overrides: Pick<T, SubmitKeys>,
): Omit<IFluidParentContext & IFluidRootParentContext, SubmitKeys> & Pick<T, SubmitKeys> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a good time to plug this PR of mine I never pushed through #23694 Review comments welcome even as it's closed, I do expect to reopen it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable should probably go next to Deep/ShallowReadonly in core-interfaces

@jason-ha
Copy link
Contributor Author

Now all caught up and reconciled with main.

setConnectionState(connected: boolean, clientId?: string): void;
// (undocumented)
submitSignal(type: string, content: any): null;
submitSignal: IFluidDataStoreRuntime["submitSignal"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is unfortunate. Makes it harder to reason about signature changes that could be made in the future. How critical is it that we explicitly annotate the typing like this? Can we verify compatibility another way such that the signature here remains explicit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type-tests should catch breaking changes made here, so I don't think this is a blocking issue or anything. Would just be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it unfortunate?
The signature for IFluidDataStoreRuntime.submitSignal was changes to (type: string, content: unknown, targetClientId?: string) => void 19 months ago. There was no visit here. This will keep its type up to date.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate because if IFluidDataStoreRuntime.submitSignal changes again (potentially in a breaking way), there will be no change to the report here, and therefore no indication that a breaking change affected this package (aside from type-test changes for the cases that they detect).

Ideally, API-Extractor could inline things in a case like this, and we could have the best of both worlds, but I'm not holding my breath for that functionality.

I'm not sure what the best answer here is. Presumably we could write a type-test to enforce (at build time) that the signature here matches the signature of IFluidDataStoreRuntime.submitSignal, while keeping the explicit signature. But I don't know if that would be worth the effort.

Again, not blocking on this, just wondering what the best pattern is to ensure we can easily detect changes here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Note that there was no reported change before for at least some cases of breaking change to IFluidDataStoreRuntime.submitSignal.
I think the IFluidDataStoreRuntime.submitSignal change should be the larger signal to anything in this space.

Copy link
Contributor

@Josmithr Josmithr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API changes look good, assuming we're in the window of time where we can make beta breaks.

@github-actions
Copy link
Contributor

🔗 Found some broken links! 💔

Run a link check locally to find them. See
https://github.com/microsoft/FluidFramework/wiki/Checking-for-broken-links-in-the-documentation for more information.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

 ELIFECYCLE  Command failed with exit code 1.

@jason-ha
Copy link
Contributor Author

API changes look good, assuming we're in the window of time where we can make beta breaks.

I think you are referring to FluidDataStoreRuntime change to mark its implemented rollback function as not optional. I think that is okay to change, but it is not required so I have reverted that.

The generic addition to IEnvelope should not be breaking as the default gives you the same type as before.

If you are concerned about another one of the changes, LMK.

@WillieHabi WillieHabi self-requested a review October 31, 2025 22:14
/**
* Submits the signal to be sent to other clients.
* @param envelope - {@link IEnvelope} containing the signal address and contents.
* @param targetClientId - When specified, the signal is only sent to the provided client id.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

Suggested change
* @param targetClientId - When specified, the signal is only sent to the provided client id.
* @param targetClientId - When specified, the signal is only sent to the provided client ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(All I did was copy-paste) I will get it in a follow-up.

@jason-ha jason-ha merged commit 8ed4f25 into main Oct 31, 2025
49 checks passed
@jason-ha jason-ha deleted the test/client/ops/internal-runtime-type-tightening branch October 31, 2025 22:58
anthony-murphy-agent pushed a commit to anthony-murphy-agent/FluidFramework that referenced this pull request Jan 14, 2026
…ack, and submitSignal type-safety (microsoft#24319)

improvement(client): improved internal submitMessage, reSubmit,
rollback, and submitSignal type-safety

In Legacy API:
- Add type parameter to `IEnvelope` for `contents` to support stricter
typing where desired.
- Remove unused parameters from mock `submitMessage` and `submitSignal`
methods.
- Clean up some mock types.

Internally:
- Replace `any` in `ISignalEnvelope` with `unknown`.
- Relocate `FluidDataStoreMessage` to runtime-definitions. Eventually
this can serve as basis for message types for DataStores.
- In `datastore`, create `LocalFluidDataStoreRuntimeMessage`
representing the two known messages for DataStores.
- `FluidDataStoreContext` requires `OutboundFluidDataStoreMessage` for
stricter `reSubmit` and `rollback` methods.
- Create `IFluidRootParentContextPrivate` for the transition from
DataStore to `ContainerRuntime` and use explicit types in
`ContainerRuntime` and `ChannelCollection`.
- In `ChannelCollection` restore some old method names with strict
typing that internal callers must conform to. The
`IFluidDataStoreChannel` portions that were not required by current use
were refactored to new `ComposableChannelCollection` class.
- Replace `wrapContext` with `formParentContext` that handles both
`IFluidParentContextPrivate` and `IFluidRootParentContextPrivate` by
requiring the specific `submitMessage` and `submitSignal` functions
- Within `ContainerRuntime`:
  - Update `submitMessage` to expect specific messages
- Update `submitSignal` to expect new
`AddressedUnsequencedSignalEnvelope` that is specific subset of
`ISignalEnvelope` and is exactly
`IEnvelope<ISignalEnvelope["contents"]>`
- Fixup tests to use more realistic data (as required by stricter
typing).

Potential Future Improvements:
- Change {@link IFluidDataStoreChannel} and {@link IFluidParentContext},
to have a generic specifying `T extends FluidDataStoreMessage` and uses
`T["type"]` and `T["content"]` to qualify message related methods,
preferably where `submitMessage`, `reSubmit`, and `rollback` have
overloads to ensure callers pair values correctly.
- A further improvement would be to reshape `submitMessage`, `reSubmit`,
and `rollback` to accept `T` as `message` parameter instead of `type`
and `content` parameters that are hard to convince TypeScript must be
paired in implementations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: runtime Runtime related issues area: tests Tests to add, test infrastructure improvements, etc base: main PRs targeted against main branch public api change Changes to a public API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants