Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

jason-ha
Copy link
Contributor

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

In Legacy API:

  • Add type parameter to IEnvelope for contents to support stricter typing where desired.
  • Deprecate submitMessage on FluidDataStoreRuntime and MockFluidDataStoreRuntimeas not required perIFluidDataStoreChannel. IFluidParentContext(which is base interface forIFluidDataStoreContext) 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.

…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 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 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 = (

Comment on lines +1062 to 1065
/**
* @deprecated No implementation required and will be removed in 2.50.
*/
public submitMessage(type: DataStoreMessageType, content: any, localOpMetadata: unknown) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This deprecation is not required. Just seemed like it might be stale and a clean-up opportunity.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I've noticed this isn't used too I think. The asymmetry with submitSignal caught my eye, but I guess the point is, op semantics are internal to the FluidDataStore/DDS implementations, and DataStore authors don't get to generate them manually. Signals is an open protocol that the runtime makes available to DataStore authors.

Copy link
Member

Choose a reason for hiding this comment

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

(hence submitMessage not existing on IFluidDataStoreRuntime)

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`.
Copy link
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

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

Crawling...

Stats:
  163664 links
    1315 destination URLs
    1547 URLs ignored
       0 warnings
       0 errors


* @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?

*
* @privateRemarks
* `type` parameter's type of `DataStoreMessageType` is a covariance exception
* over `string` that `IFluidDataStoreChannel` specifies. (`unreachableCase`
Copy link
Member

Choose a reason for hiding this comment

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

Some thoughts on this - It's strictly true, but only messages that were submitted from here will come back for resubmit. So IMO it is a legitimate, stable and robust way to type this. I don't think the warning about unreachableCase being hit over time is warranted.

I believe it's correct that IFluidDataStoreChannel should specify string here, since another implementation could have its own schema. And we haven't wanted to parametrize everything, it leads to quite a soup of generics and doesn't provide much value in practice IMO.

*
* @privateRemarks
* `type` parameter's type of `DataStoreMessageType` is a covariance exception
* over `string` that `IFluidDataStoreChannel` specifies.
Copy link
Member

Choose a reason for hiding this comment

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

Same as above. If you're going to call this out, please also point out that the only values passed in here at runtime are those passed out via this.dataStoreContext.submitMessage

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do believe that is the intent, yet the contract (typing) doesn't say that is definitely the case. It is up to the caller to respect that and not have bugs. I am pretty sure there are improvements that can be made to provide safety and help with readability.
But, yes, we can note the intent in comments in the meantime.

* Type of the op, within the ContainerRuntime's domain
*/
type: TType;
// eslint-disable-next-line @typescript-eslint/no-namespace
Copy link
Member

Choose a reason for hiding this comment

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

Talk to me about namespace. What does it do, why is the linter saying don't do it, and why is it so rare in our codebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't get answers to why the linter says don't do it. It may stem from when there was a bunch of churn in the space with ESM modules and confusing terminology.
This was more useful earlier when I was exploring things and it looked like this might leak out. Can and probably should just roll this back.

}

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.

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

* @internal
* @privateRemarks Exposed per ChannelCollection testing and API extractor request
*/
export interface IFluidRootParentContext
Copy link
Member

Choose a reason for hiding this comment

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

You'll need to rationalize splitting this out with the new IFluidParentContextPrivate added by #24465

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe "reconcile" is what you meant. Yeah, probably having this first (proposed way before) would have been good. This goes a lot further on splitting out the nested datastore support and might simplify or clarify things.

@@ -708,11 +711,11 @@ export class ContainerRuntime
extends TypedEventEmitter<IContainerRuntimeEvents>
implements
IContainerRuntime,
Omit<IFluidRootParentContext, "submitSignal">,
Copy link
Member

Choose a reason for hiding this comment

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

Toss a comment here explaining why the mismatch? (it's a type mismatch that leads you to omit this right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do. It is not a type mismatch. ContainerRuntime doesn't need to implement any of IFluidRootParentContext. It does implement most of it so that it can hand itself over to the formParentContext function to make an IFluidRootParentContext. So, the utility here is to force type checking on those members in the class versus there being a type failure when this is given to formParentContext.

@@ -4461,7 +4464,7 @@ export class ContainerRuntime
case ContainerMessageType.Alias: {
// For Operations, call resubmitDataStoreOp which will find the right store
Copy link
Member

Choose a reason for hiding this comment

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

Out of date / out of context comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps a little out of date. I suspect this case only handled FluidDataStoreOp to begin with and was expanded for Attach and Alias later.


Implementing `FluidDataStoreRuntime.submitMessage` is not required per `IFluidDataStoreChannel` and is now deprecated on `FluidDataStoreRuntime` and corresponding `MockFluidDataStoreRuntime`.

See [issue #24406](https://github.com/microsoft/FluidFramework/issues/24406) for details and alternatives.
Copy link
Member

Choose a reason for hiding this comment

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

That issue mentions:

IFluidParentContext (the base interface for IFluidDataStoreContext) should always be used to access submitMessage functionality.

That may be true for inside FluidDataStoreRuntime class, but as for the public API to submit an op, I think the comment should just be - "Submitting arbitrary messages is not supported"

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 FluidDataStoreRuntime a class customers are expected to derive from? I don't think Pages does and I see that we do internally in at least two places.
Feel free edit the issue.
We should seal the class if no one should derive from it. Then it will be easier to blanketly say don't call.

Copy link
Member

@markfields markfields left a comment

Choose a reason for hiding this comment

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

LGTM overall! I'll take another pass once you're all merged up and have made the changes you're mentioning in comments

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 changeset-present public api change Changes to a public API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants