Skip to content

Add readonly API to IFluidContainer #24521

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

Conversation

yunho-microsoft
Copy link
Contributor

@yunho-microsoft yunho-microsoft commented May 2, 2025

Description

Add readonly API to IFluidContainer. This makes users of ODSPClient able to get the readonly status of the container. Also expose the readonly event handler.

Testing: add a new test to check readonly flag after container attachment. Also test the readonly event handler by triggering the forceReadonly function in IContainer

Reviewer Guidance

The review process is outlined on this wiki page.

@Copilot Copilot AI review requested due to automatic review settings May 2, 2025 22:12
@yunho-microsoft yunho-microsoft requested a review from a team as a code owner May 2, 2025 22:12
@github-actions github-actions bot added base: main PRs targeted against main branch area: dds Issues related to distributed data structures area: dds: tree area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: loader Loader related issues public api change Changes to a public API labels May 2, 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.

Pull Request Overview

This PR introduces a readonly API to IFluidContainer, allowing users to inspect and enforce readonly mode on containers. Key changes include:

  • Addition of a new readonly property and forceReadonly method to IFluidContainer and its implementation.
  • Emitting a new "readonly" event when the container’s readonly state changes.
  • Updates to API reports and end-to-end tests to support the new readonly functionality.

Reviewed Changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/service-clients/end-to-end-tests/odsp-client/src/test/containerCreate.spec.ts Adds a new test validating the readonly flag and event firing.
packages/loader/container-loader/src/connectionManager.ts Inserts a debug log in connectCore; likely a leftover debugging aid.
packages/framework/fluid-static/src/fluidContainer.ts Implements and wires up the readonly API changes.
API Report files Updates API definitions for public, beta, alpha, and legacy releases.
packages/dds/tree/api-report/tree.alpha.api.md Updates readonly API documentation in tree API reports.

@yunho-microsoft yunho-microsoft requested a review from vladsud May 2, 2025 22:29
@vladsud vladsud requested a review from jason-ha May 2, 2025 22:38
@vladsud
Copy link
Contributor

vladsud commented May 2, 2025

@jason-ha, could you please help Yunpeng to get it in? I.e. guide on what's required to expose new functionality (and not break any rules around versioning). Thanks!

@github-actions github-actions bot removed area: dds: tree area: dds Issues related to distributed data structures labels May 6, 2025
@github-actions github-actions bot removed the area: loader Loader related issues label May 6, 2025
* This is used to determine if the container is read-only or not.
* undefined means that the read-only state is not known yet, like when container is not connected.
*/
readonly readOnly: boolean | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

While this would be different than others, I recommend that all new "properties" that are dynamic (and especially if they will be implemented with logic) be written a method. This could be isReadOnly() or getReadOnlyState().
cc @microsoft/fluid-cr-api

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed and use getReadOnlyState

Copy link
Contributor

Choose a reason for hiding this comment

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

That update works for me. I would like API v-team to confirm this. (unresolving for visibility)
@microsoft/fluid-cr-api?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm personally OK with any of the above - afaik we haven't established a strict rule about whether dynamic members should be properties/methods. My slight preference is for readOnly > isReadOnly() > getReadOnlyState(). "State" seems to imply a more complex return type than Boolean, and "is" is always a nice prefix for Boolean.

My personal rule is to switch to a method if it requires computation (particularly potentially expensive computation).
Rather than switching to a method just for being dynamic.

Here's what we have currently:
https://github.com/microsoft/FluidFramework/wiki/Coding-Guidelines#naming

Copy link
Contributor

Choose a reason for hiding this comment

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

There is compute. At least on the API surface (though it is informative internally too), a method helps callers know that it is not a simple property (maybe it is dynamic or maybe complex) and they shouldn't capture it simply and expect it to remain the same.
Interestingly for connected the compute appears light but can be incredibly deep. I think I counted something around 20-25 frames when debugging once to get to the real source of state.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't mind a method here, and of course "how much compute is too much for a property" is a little fuzzy. I also have mixed feelings about our immutable-but-lazily-instantiated properties that might be expensive on first access.

There are plenty of examples of dynamic values in native JS APIs that represent as properties though (e.g. Array.length), so while I respect the desire to hint that capturing is not appropriate I think it can be OK to explain this in documentation in some cases.

Acknowledged that this is probably a good topic for API council to discuss in more detail and come up with something consistent that we can recommend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on discussion, I decide to use isReadOnly as the member function name and the event name is readonlyChanged. @jason-ha , @ChumpChief Please take a look

* This is used to determine if the container is read-only or not.
* undefined means that the read-only state is not known yet, like when container is not connected.
*/
getReadOnlyState(): boolean | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this a plain boolean? "undefined" is confusing here, it would be nice to have a clear signal to the customer "should I try writing or not"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ChumpChief So you mean return true if the flag is actually undefined? Internally we have the flag as type boolean | undefined. And as the comment states, the readOnly flag is unknown when connection is not established yet. I believe the write will fail if client tries to write when readOnly is undefined since no connection yet. So I prefer keep it same as the interal type. @vladsud Any comments

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is proving my point that it is confusing :) By returning undefined, it pushes the work to the customer to interpret what that means and what they should do in response. A friendlier API just lets them know "go ahead and write" vs. "don't try to write".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Make it return boolean. Undefined -> true for read only value. Please take a look. @ChumpChief

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather make it a tri-state (enum), i.e. push to caller to decide what to do when we have no information (undefined means - we have no clue if file is read-only or not). While we should provide some guidance (like - in most cases if you do not have this info, you can assume the container is writable, and handling of situation is not much different from other places where client learns about write -> read changes with delay and needs to deal with it), I think we should not make a call for the caller on what to do here, especially that it would be (statistically speaking) the majority of cases where client would operate with read-only files without learning yet that it's read-only file.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what you are saying, and so I don't think a customer would either.

What would you put in the documentation to a customer for the "undefined" state? Should they permit write attempts or not? The customer's decision of what to do is boolean at the end of the day, I don't see why we should make them deduce what the correct boolean decision is from a tri-state.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can I ask, what might be done with container that is not yet connected and we don't know compared to when known and read-only or writable?
Maybe what should/should not be done when unknown? If caller does attempt to write before connection and then there is no write access, what happens?
If we can or want to treat the unknown case like one of the others, then fine we can just use a boolean. If special consideration is needed, then we should use a tri-state and the property may need to be renamed to not appear as binary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jason-ha I prefer to use tri-state. And the documentation can clarify that if readonly is undefined/unknown, it means the flag is unknown and container disconnected, please check container's connected state.
And in this way we give whatever we know about the flag and let developers decide what to do next. It also simplify the logic between IFluidContainer layer and IContainer layer for flag translation (from undefined to true/false) and saves the potential event changes since we raise event when flag turns from undefined -> true/false (we need to omit the event if we decide to make undefined -> true/false). Actually I am not sure what value to put there (true or false?) when the internal state is unknown.

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:
  210260 links
    1663 destination URLs
    1895 URLs ignored
       0 warnings
       0 errors


@@ -43,6 +44,7 @@ export interface IFluidContainerEvents extends IEvent {
(event: "saved", listener: () => void): void;
(event: "dirty", listener: () => void): void;
(event: "disposed", listener: (error?: ICriticalContainerError) => void): any;
(event: "readonlyChanged", listener: (readonly: boolean) => void): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should match camel casing between the event and property - either isReadOnly/readOnlyChanged or isReadonly/readonlyChanged. Otherwise this naming and use of method looks good to me!

@@ -324,6 +346,10 @@ class FluidContainer<TContainerSchema extends ContainerSchema = ContainerSchema>
return this.rootDataObject.initialObjects as InitialObjects<TContainerSchema>;
}

public isReadOnly(): boolean {
return this.container.readOnlyInfo.readonly ?? true;
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like undefined is more commonly equated with false in container.ts - probably check with @vladsud what he'd recommend doing in the undefined case.

Also, you should make sure that the readonlyChanged event is only emitted when the computed value of this property actually changes (so, for example it shouldn't emit for container.readOnlyInfo.readonly going from undefined -> true as currently written here since isReadOnly() would return true both before and after that state change). I see some guards in connectionManager.ts that suppress raising the event if the state didn't change, but it's not immediately clear if that will prevent the above possibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's part of the reason I prefer keep undefined. This PR is to exposing the readonly information to IFluidContainer rather than fixing any existing logic. And internally we emit the event when trying to set the readonly flag. undefined -> true is a readonly state change (from unknown to true/false).

Copy link
Contributor

Choose a reason for hiding this comment

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

We should be able to resolve this here though, one direction or the other. The public API surface has a higher quality bar than the legacy API surface given its longer support/compat contract, and also because it needs to be consumable by third parties who we may not be able to directly tutorialize on the quirks of the underlying system.

Copy link
Contributor

Choose a reason for hiding this comment

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

Left a comment in above comment - undefined means we have no clue. We need socket connection to learn if user has write access to a file.

@yunho-microsoft
Copy link
Contributor Author

Changes moved to clients' repo

@yunho-microsoft yunho-microsoft deleted the yunho/addReadOnlyToOdspClient branch June 13, 2025 21:56
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 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.

5 participants