-
Notifications
You must be signed in to change notification settings - Fork 523
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
Type Erase Shared object kinds #21081
Type Erase Shared object kinds #21081
Conversation
* @public | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface SharedObjectKind<out TSharedObject = unknown> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the important new type in this change. The old ISharedObjectKind is appropriate for the encapsulated API, and this is a type erased version removing its members for use in the declarative API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to start all interfaces with "I".
I'm not sure that dropping I makes it clearer on what type actually means / where to use it.
Should there be any limitations on TSharedObject? Like extend ISharedObject?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to start all interfaces with "I".
I'm not sure that dropping I makes it clearer on what type actually means / where to use it.
Generally, we have been naming types with members that define some sort of contract, usually a collection of methods "I" something.
This type is not like that. It has no members and is not implementable. Its an opaque token as far as the user of it is concerned.
It is defined using interface
because TSC produces better IntelliSense and error messages if we use interface
then if we use type
in this case (using interface introduces a name that can occur in messages while type
does not).
As an implementation detail this type actually extends the ErasedType class (as thats the only want to make a non-implementable type), so its not even a real interface from the class vs interface perspective.
Thus I think not having an "I" is acceptable here.
The version with the "I" is an actual interface which is alpha/legacy, actually legal and safe to implement, and has the API surface needed for the encapsulated API. Only SharedObjectKind
is public, so public users shouldn't get confused.
Encapsulated API users should stick with the one that actually has members they can access, but getting it wrong isn't very risky as the compiler will let you know and the runtime behavior would work in most cases anyway.
That was my reasoning, but if you have other name suggestions, I'd be happy to consider them. This was just the first option I thought of, and seemed ok and didn't require renaming existing types.
Should there be any limitations on TSharedObject? Like extend ISharedObject?
I think not, but thats not a strong opinion.
I don't think adding such an extends clause gains us anything (other than perhaps some documentation, which might be worth it). Its mainly helpful if for code works with SharedObjectKinds without using strong typing of its output, which we don't do (since we down cast to internal types first), and if someone did want such code, they can just include the constraint it in their own code. Generally I find it cleaner to leave the constrains out unless we have a reason to include them.
For this specific case if we constrained it to ISharedObject, that would add a bunch of types back to the public API. If we wanted a constraint here IFluidLoadable would be the good choice as thats all ITree extends publicly.
Additionally when we actually stabilize any DataObjects, they can work via this type without us adding any extra API surface (they work correctly with it currently: we just don't have any public non-experimental data objects), so that would also suggest IFluidLoadable if we want a constraint here.
Lastly as this is a type only we implement, adding constraints to is non-breaking, but removing them is breaking, which is another reason to side on leaving it unconstrained for now.
@@ -84,6 +85,9 @@ export interface ContainerRuntimeFactoryWithDefaultDataStoreProps { | |||
runtimeOptions?: IContainerRuntimeOptions; | |||
} | |||
|
|||
// @internal | |||
export function createDataObjectKind<T extends new (...any: any[]) => DataObject>(factory: T): T & SharedObjectKind<InstanceType<T>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Unified the APi shape/types for data objects in the declarative API, avoiding adding any extra typing to support them.
type ReadOnlyInfo, | ||
type IConnectionDetails, | ||
type IDeltaQueueEvents, | ||
} from "@fluidframework/container-definitions/internal"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
due to our API extractor configuration, these types had to be exported. I'm not clear on the exact cause, but with how package boundaries are handled its not surprising.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would imply that we added something new to the API here that required pulling all those things in. Can we not just remove whatever that was? I really don't think we want to expose the delta manager types if we can avoid it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fluid-framework: Error: /home/craig/Work/FluidFramework.git/proxy/packages/dds/shared-object-base/src/sharedObject.ts:145:2 - (ae-forgotten-export) The symbol "IDeltaManager" needs to be exported by the entry point index.d.ts
So thats from SharedObjectCore.
fluid-framework: Error: /home/craig/Work/FluidFramework.git/proxy/packages/dds/shared-object-base/src/sharedObject.ts:626:1 - (ae-forgotten-export) The symbol "SharedObjectCore" needs to be exported by the entry point index.d.ts
So thats from SharedObject.
fluid-framework: Error: /home/craig/Work/FluidFramework.git/proxy/packages/dds/sequence/src/sequence.ts:116:1 - (ae-forgotten-export) The symbol "SharedObject" needs to be exported by the entry point index.d.ts
Which we need because of SharedSegmentSequence. Which was already exported.
No change was made to the relevant exports which depend on these things.
What did change is we explicitly export something @fluidframework/shared-object-base, which causes to have to export all direct dependencies of existing exports that are from that package, and all the things they reference in packages we explicitly export from recursively.
While this could be fixed by not exporting anything from @fluidframework/shared-object-base, I don't think thats a good solution as we want to export SharedObjectKind, and moving it to another package to doge this issue would be silly.
I tried to clean this up by requiring all transitive referenced types to be exported in #20554 to avoid this mess and make thing much more clear, but I was unable to get support for that change.
Thus I don't see what I should do other than what I did here, which I agree is bad and confusing. I'm open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ae-forgotten-exports
is not currently enabled in this package, right? Or was that enabled at some point?
If any of these types were already transitively referenced, then I'm concerned that they weren't already an issue. If they are newly transitively referenced, then I think we need to find a solution that doesn't leak them. Or we can re-disable the forgotten exports rule until we fixed things here. I would definitely prefer the former, but the latter is probably fine if we follow up quickly.
@jason-ha FYI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the docs in common/build/build-common/api-extractor-lint.json, I think we suppress ae-forgotten-export giving it log level none, but thats only about cross package stuff? The config isn't super clear to me.
|
||
// @public | ||
export type ContainerAttachProps<T = unknown> = T; | ||
|
||
// @public | ||
export interface ContainerSchema { | ||
readonly dynamicObjectTypes?: readonly LoadableObjectClass[]; | ||
readonly initialObjects: LoadableObjectClassRecord; | ||
readonly dynamicObjectTypes?: readonly SharedObjectKind[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the other half of the main public APi change: using SharedObjectKind instead of LoadableObjectClass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
someone owning this space should take a look. @ChumpChief , @sashasimic, can someone take a look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LoadableObjectClass currently permits DataObjects, e.g. Signaler, so this change will change the functional scenarios we support. Is that intentional, or are you only intending to change typing while retaining the same scenario support?
I'm personally OK with removing the support as it was only declared "beta", but then you'll need to follow this up with documentation updates to reflect the reduced support (e.g. https://fluidframework.com/docs/build/data-modeling/#creating-a-dynamic-object, probably some tsdoc in various places, probably others).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still support data objects. No change to what works, they just use an identical public API to shared objects now. I even added a utility to aqueduct to create data objects with the correct typers to with this updated API, and updated data objects tests and examples to align with it.
The public facing docs don't mention this as data objects aren't available publicly in any way that's observable different from shared objects but if we did publish a DataObject as public (for example the experimental one in our tree-react-utils), they could just use it as if it were a shared object and it would just work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs on SharedObjectKind do call out that it can be a DataObject, but only in internal facing docs.
@@ -89,7 +89,7 @@ export interface IDeltaHandler { | |||
// @public | |||
export type IDeltaManagerErased = ErasedType<"@fluidframework/container-definitions.IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>">; | |||
|
|||
// @public @sealed | |||
// @alpha @sealed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several types moved from public to alpha in here, including IFluidDataStoreRuntime
@@ -289,7 +289,7 @@ export interface IFluidParentContext extends IProvideFluidHandleContext, Partial | |||
uploadBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise<IFluidHandleInternal<ArrayBufferLike>>; | |||
} | |||
|
|||
// @public | |||
// @alpha |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another newly non public API
@@ -43,6 +43,13 @@ import { TestFluidObjectFactory } from "@fluidframework/test-utils/internal"; | |||
// ContainerRuntime and Data Runtime API | |||
import * as semver from "semver"; | |||
|
|||
// TypeScript generates incorrect imports in the d.ts file if this is not included. | |||
import { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparent TSC bug workaround.
TODO: follow-up on this (report for find open issue)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a work item tracking your/our portion of follow-up?
* @public | ||
*/ | ||
// eslint-disable-next-line import/export | ||
export const SharedTree: SharedObjectKind<ITree> = OriginalSharedTree; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we reexport SharedTree, but with the alpha types (ISharedObjectKind) removed, just keeping the SharedObjectKind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should any of your PR comments make it into code comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have improved the docs comments a bit. Let me know if there is anything you spot thats still missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I missed this file, I'll improve its docs in a moment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be better now.
⯅ @fluid-example/bundle-size-tests: +21 Bytes
Baseline commit: 2388680 |
packages/service-clients/azure-client/api-report/azure-client.api.md
Outdated
Show resolved
Hide resolved
@@ -54,7 +54,7 @@ export interface ISharedObjectEvents extends IErrorEvent { | |||
|
|||
/** | |||
* Base interface for shared objects from which other interfaces derive. Implemented by SharedObject | |||
* @public | |||
* @alpha |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine with me, but maybe worth doble checking that the message is - we do not support 3rd parties creating DDSs. Is that actually the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that is correct. @kashms can you confirm this is still the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not support users of the declarative API doing creating DDSes. If they were going to implement a custom one, they would need to use alpha APIs (the encapsulated API) anyway since SharedObject (the class) is alpha as are a lots of things they would need to interact with (like the DeltaManager).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, @Josmithr alpha(aka /legacy export) is the right designation here for custom DDSes in FF 2
"@fluidframework/fluid-static": minor | ||
"@fluidframework/runtime-definitions": minor | ||
"@fluidframework/shared-object-base": minor | ||
"@fluidframework/tree": minor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like it just be major in most places. Not sure it matters (based on where are with our releases).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our current workflow doesn't use this in a user facing way, and leaving it at the default (computed by the changeset tool) seems to be the simplest option for our current release process. Its not ideal, but it will work more logically once we are post 2.0 (and then the main branch defaulting to minor will be correct data we don't use instead of ambiguous data we don't use).
* @public | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface SharedObjectKind<out TSharedObject = unknown> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend creating some readme.md that talks more in detail these types and intended use, and what problems they solve. Just glancing at this type (or comments above), it's not very clear on what is intended usage. Maybe we should rely on developers glancing at current usage in repo to imply these answers, but it feels like having a bit more English would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
I have extended the doc comments here quite a bit. I picked putting the text there as its easier to find/link from the code and the readme is more public/use facing and this seems like developer facing info mostly.
If you have anything further you think I should add, let me know.
@@ -21,7 +22,7 @@ export interface ISharedCounterEvents extends ISharedObjectEvents { | |||
} | |||
|
|||
// @alpha | |||
export const SharedCounter: ISharedObjectKind<ISharedCounter>; | |||
export const SharedCounter: ISharedObjectKind<ISharedCounter> & SharedObjectKind<ISharedCounter>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it true that every place where we use ISharedObjectKind we will overlap with SharedObjectKind? If yes, should we reconsider how ISharedObjectKind is defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it were practical to do so yes. Sadly I don't think there is a good way to do it.
When dealing with type erasure and unimplementable interfaces, this gets a bit nasty.
If we make ISharedObjectKind extend SharedObjectKind, then its impossible to get typescript to type check an implementation of ISharedObjectKind since SharedObjectKind goes out of its way to opt out of structural typing and not be implementable (which it has to, or all values would be usable as ISharedObjectKinds).
If someone is using the encapsulated API, them manually implementing ISharedObjectKind is probably the main reason they would use the type, and it works fine with how the code is now. You only hit this problem if you write code which needs to work with both the encapsulated API, and also the declarative API. Currently I expect only our repo to do that, and it deduplicates all the type nastiness in one function (createSharedObjectKind): this type is implicitly inferred in the implementation as the return value from that function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated some of the relevtn doc comments to help clarify this.
type ReadOnlyInfo, | ||
type IConnectionDetails, | ||
type IDeltaQueueEvents, | ||
} from "@fluidframework/container-definitions/internal"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if it's stupid question (have not looked at fluid-framework), but does this reexport essentially makes APIs "public"? I hope the answer is no, but worth checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-exports have no impact on the tagging. These types, which were transitively reachable from existing legacy/alpha API are simply being added to the legacy exports here, but they were already available under legacy on other packages. They were added because API extractor is configured to force it with the current rather indecisive config which forces including some transitive deps but not others (and unrelated changes in this PR impacted which transitive's it requires by impacting which types and packages are referenced directly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its a topic of some debate current if fluid-framework/legacy counts as "public" or more public than legacy on other packages which is why I was all wordy and specific there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving for the changes to devtools.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it!
@@ -186,7 +186,7 @@ function mapContainerProps( | |||
return { | |||
container: innerContainer, | |||
containerKey, | |||
containerData: container.initialObjects, | |||
containerData: container.initialObjects as Record<string, IFluidLoadable>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen as
get us into trouble plenty. Is there nothing better here? Some debt we should follow up on?
@@ -43,6 +43,13 @@ import { TestFluidObjectFactory } from "@fluidframework/test-utils/internal"; | |||
// ContainerRuntime and Data Runtime API | |||
import * as semver from "semver"; | |||
|
|||
// TypeScript generates incorrect imports in the d.ts file if this is not included. | |||
import { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a work item tracking your/our portion of follow-up?
## Description Make ISharedObjectKind, IChannel, IChannelFactory, IFluidDataStoreRuntime and more alpha by tweaking fluid-static APIs typing. ## Breaking Changes Access to these now less public types should not be required for users of the `@public` "declarative API" exposed in the `fluid-framework` package, but can still be accessed for those who need them under the `/legacy` import paths. The full list of such types is: - `SharedTree` as exported from `@fluidframwork/tree`: It is still exported as `@public` from `fluid-framework` as `SharedObjectKind`. - `ISharedObjectKind`: See new `SharedObjectKind` type for use in `@public` APIs. `ISharedObject` - `IChannel` - `IChannelAttributes` - `IChannelFactory` - `IExperimentalIncrementalSummaryContext` - `IGarbageCollectionData` - `ISummaryStats` - `ISummaryTreeWithStats` - `ITelemetryContext` - `IDeltaManagerErased` - `IFluidDataStoreRuntimeEvents` - `IFluidHandleContext` - `IProvideFluidHandleContext` Removed APIs: - `DataObjectClass`: Usages replaced with `SharedObjectKind`. - `LoadableObjectClass`: Replaced with `SharedObjectKind`. - `LoadableObjectClassRecord`: Replaced with `Record<string, SharedObjectKind>`.
Description
Make ISharedObjectKind, IChannel, IChannelFactory, IFluidDataStoreRuntime and more alpha by tweaking fluid-static APIs typing.
Breaking Changes
Access to these now less public types should not be required for users of the
@public
"declarative API" exposed in thefluid-framework
package, but can still be accessed for those who need them under the/legacy
import paths.The full list of such types is:
SharedTree
as exported from@fluidframwork/tree
: It is still exported as@public
fromfluid-framework
asSharedObjectKind
.ISharedObjectKind
: See newSharedObjectKind
type for use in@public
APIs.ISharedObject
IChannel
IChannelAttributes
IChannelFactory
IExperimentalIncrementalSummaryContext
IGarbageCollectionData
ISummaryStats
ISummaryTreeWithStats
ITelemetryContext
IDeltaManagerErased
IFluidDataStoreRuntimeEvents
IFluidHandleContext
IProvideFluidHandleContext
Removed APIs:
DataObjectClass
: Usages replaced withSharedObjectKind
.LoadableObjectClass
: Replaced withSharedObjectKind
.LoadableObjectClassRecord
: Replaced withRecord<string, SharedObjectKind>
.Reviewer Guidance
The review process is outlined on this wiki page.