I'm trying to type an object that is observable and uses a custom Symbol.observable ponyfill (similar to the system-observable ponyfill; code shown below), but I can't figure out a way to do it.
I've tried using typeof $$observable for the key and tried making Symbol.observable a unique symbol, but neither of those worked. I've had to resort to casting the object, but the problem with that approach is that I then have to cast the entire object (not shown below) and that makes it harder to catch other type errors due to other properties in the object being incorrectly typed. I also can't just do ReturnType<typeof makeObservableContainer> because I'd like to have it defined in a separate interface without having to depend on the function.
I would alternatively be interested in knowing if there's a way to cast it on a more fine-grained level than casting the entire object (if it turns out to be impossible to do without casting).
I've provided a playground link. Any help would be appreciated.
First attempt (no casting)
Error message
Property '[Symbol.observable]' is missing in type '{ [x: string]: () => { [x: string]: ((observer: Observer) => { unsubscribe(): void; }) | (() => { [x: string]: ((observer: Observer) => { unsubscribe(): void; }) | ...; subscribe(observer: Observer): { ...; }; }); subscribe(observer: Observer): { ...; }; }; }' but required in type 'Container'.(2741)
Code
// system-observable.ts
declare global {
interface SymbolConstructor {
readonly observable: symbol
}
}
const $$observable = /* #__PURE__ */ (() =>
(typeof Symbol === 'function' && Symbol.observable) || '@@observable')()
export default $$observable
// observer-creator.ts
interface Observer {
next(): void;
}
interface Observable {
subscribe: (observer: Observer) => { unsubscribe(): void };
[Symbol.observable](): Observable;
}
interface Container {
// How do I type this?
[Symbol.observable](): Observable;
}
function makeObservableContainer(): Container {
// error here because this is not assignable to `Container`
return {
[$$observable]() {
return {
subscribe(observer: Observer) {
// TODO
return {
unsubscribe() {
// TODO
}
}
},
[$$observable]() { return this }
}
},
// other properties that should conform to `Container` interface
}
}
Playground Link: Provided
Second attempt (unique symbol and a little bit of casting)
Error
Type '() => { subscribe(observer: Observer): { unsubscribe(): void; }; observable: Observable; }' is not assignable to type '() => Observable'. Property '[Symbol.observable]' is missing in type '{ subscribe(observer: Observer): { unsubscribe(): void; }; observable: Observable; }' but required in type 'Observable'.(2322)
Code
// system-observable.ts
declare global {
interface SymbolConstructor {
readonly observable: unique symbol
}
}
const $$observable: SymbolConstructor['observable'] = /* #__PURE__ */ (() =>
(typeof Symbol === 'function' && Symbol.observable) || '@@observable')() as SymbolConstructor['observable']
export default $$observable
// observer-creator.ts
interface Observer {
next(): void;
}
interface Observable {
subscribe: (observer: Observer) => { unsubscribe(): void };
[Symbol.observable](): Observable;
}
interface Container {
// How do I type this?
[Symbol.observable](): Observable;
}
function makeObservableContainer(): Container {
// error here because this is not assignable to `Container`
return {
[$$observable]() {
return {
subscribe(observer: Observer) {
// TODO
return {
unsubscribe() {
// TODO
}
}
},
[$$observable]() { return this }
}
},
// other properties that should conform to `Container` interface
}
}
Playground Link: Provided