-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(interop): allow Rx Observable to be consumed as InteropObservable at type level #6832
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
base: master
Are you sure you want to change the base?
Conversation
@@ -201,7 +201,7 @@ | |||
"tslint-etc": "1.13.10", | |||
"tslint-no-toplevel-property-access": "0.0.2", | |||
"tslint-no-unused-expression-chai": "0.0.3", | |||
"typescript": "~4.2.0", | |||
"typescript": "~4.3.0", |
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 had to bump TS version cause it changes how the .d.ts
emit works - this is the version that has allowed this computed property to be emitted. There is no explicit mention of such a behavior change in their release notes here. I've decided to just upgrade as little as I canhad to - so I've not upgraded this to 4.5
You can verify the behavior change in the TS playground - look into the .d.ts
tab (you have to switch to it cause I don't see any query param to be available so it would get open automatically)
TS playground 4.2.x
generated .d.ts
export {};
declare global {
interface SymbolConstructor {
readonly obs: symbol;
}
}
export declare class A {
}
export declare class B {
}
export declare class C {
}
export declare class D {
// oops, it's not here
}
interface WithInterop<T> {
[Symbol.obs]: () => T;
}
export declare type IsItWorking = D extends WithInterop<any> ? 1 : 0;
generated .d.ts
export {};
declare global {
interface SymbolConstructor {
readonly obs: symbol;
}
}
declare const symWrapper: {
sym: typeof Symbol.obs;
};
export declare class A {
}
export declare class B {
}
export declare class C {
}
export declare class D {
[symWrapper.sym]: () => {};
}
interface WithInterop<T> {
[Symbol.obs]: () => 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.
We need to be backwards compatible with TS 4.2 or this is a breaking change. 🤔
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 change was only required to emit different .d.ts
. It shouldn't break TS 4.2 consumers because they only consume the emitted .d.ts
.
Take a look at the 4.2 playground here. It works as expected.
We can also check the same code with TS 4.3 here and verify that with this version this property can be understood in full and used as expected, allowing Rx observable to be treated as InteropObservable
.
The only downside to the current state of this PR is that with TS 4.2 people might get errors because of this pattern if they dont use skipLibCheck
. We can suppress it with /** @ts-ignore */
though and that should make it compatible with all of the requirements.
Let me know what do you think and I will followup with any changes/answers as needed.
|
||
describe('interop', () => { | ||
it('should be possible to consume Rx Observable as InteropObservable', () => { |
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 test could be potentially simplified but I've chosen to depict a quasi-real-life situation here. if you prefer this to be written differently - let me know.
src/internal/Observable.ts
Outdated
@@ -340,7 +340,7 @@ export class Observable<T> implements Subscribable<T> { | |||
* @method Symbol.observable | |||
* @return {Observable} this instance of the observable | |||
*/ | |||
[Symbol_observable]() { | |||
[observableTypeRef.symbol](): Subscribable<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.
An explicit type annotation is required here because with the inferred Observable<T>
TS can't infer correctly the type using ObservedValueOf
and it ends up being unknown
. I think that is caused by the fact that Observable<T>["subscribe"]
is overloaded and TS is only using the last declared overload when processing a conditional type - so it fails to match the correct signature.
422293e
to
1ab362c
Compare
@@ -340,7 +340,8 @@ export class Observable<T> implements Subscribable<T> { | |||
* @method Symbol.observable | |||
* @return {Observable} this instance of the observable | |||
*/ | |||
[Symbol_observable]() { | |||
/** @ts-ignore mute errors for people without skipLibCheck on older versions of TS, remove this comment when we drop support for TS<4.3 */ |
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 has to be in a separate comment and right above the computed property for this to work 🤷♂️
Sorry I'm just getting to reviewing this. This is a big deal if things work. However we need to support TS 4.2 (not necessarily fixing this issue for 4.2, we just can't break compilation for users of 4.2) otherwise this has to land in 8.x |
@benlesh as far as I know - this doesn't break anything for TS 4.2 consumers. It just doesn't fix that issue for them. The |
Description:
This fixes the interop story for other libraries that would like to consume Rx as interop observable. This weird, weird structure ensures that the
Symbol.observable
type is emitted correctly to the.d.ts
file - it's actually not emitted directly but this computed property is left in the class declaration and its type isSymbol.observable
, so the whole thing works.I got a hint that this might work from this comment in the TS repo - since it has mentioned "dotted names". So I've figured out that it's worth a try here and, somewhat, surprisingly I've managed to get this working.
Related issue (if exists): fixes #6777