You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have a package that exports a function requiring a typed Namespace to do its thing. This Namespace has a SocketData value of { connectionId: string }. In my actual application I then set up such a namespace to provide said setup function with a such a namespace.
However, because the actual namespace I set up has additional properties in SocketData, for other parts of the application. This results in a TypeScript compiler error when trying to apply said actual namespace to that setup function.
The error stems from the fact that the _fns property on Namespace is contravariant over SocketData.
To Reproduce
Server
importtype{Namespace}from"socket.io";import{Server}from"socket.io";constio=newServer(3000,{});constns=io.of('/my-ns')asNamespace<any,any,any,{connectionId: string;otherStuff: string}>;constnsForMyPackage: Namespace<any,any,any,{connectionId: string}>=ns;// <- THIS ERRORS
Additional context
In theory, because _fns is not marked private, I can do something like this:
declareconstnsA: Namespace<any,any,any,{foo: string;bar: string}>;nsA.use((socket,next)=>{console.log(socket.data.bar.toUpperCase());next();});constnsB: Namespace<any,any,any,{foo: string}>=nsA;// BAD ASSIGNMENTconstnsC: Namespace<any,any,any,{foo: string}>;nsC.on('connection',(socketC)=>{nsB._fns[0](socketC,()=>{});// BAD CALL});
To the TypeScript compiler, BAD CALL looks fine. nsB._fns contains functions that require a Socket with SocketData of { foo: string }. nsC.on('connection', ...) provides just such a Socket. However, that middleware will try to call socket.data.bar.toUpperCase() and produce a TypeError: bar is undefined. That is why the compiler warns me at BAD ASSIGNMENT.
HOWEVER, I should never actually know anything about _fns, even less access it. That would make it impossible to do any such mischief. Then the assignment at BAD ASSIGNMENT would be safe. As even in that case, code like this would be sound:
declareconstnsA: Namespace<any,any,any,{foo: string;bar: string}>;nsA.use((socket,next)=>{// setup socket datasocket.data.foo='foo';socket.data.bar='bar';});constnsB: Namespace<any,any,any,{foo: string}>=nsA;// This would be a valid assignment if `_fns` was `private`nsB.on('connection',(socket)=>{console.log(socket.data.foo);console.log(socket.data.bar);// This would be a type error, but still work at runtime});
I took a quick glance at the Namespace source and it looks like you're keeping it public (with a /** @private */ JSDoc comment) because you need to access it in ParentNamespace.createChild. Two suggestions to deal with that after making _fns private:
Use namespace['_fns'] = this._fns.slice() in createChild
Provide a protected static setFns(ns: Namespace, fns: Array<...>) { ns._fns = fns; } in Namespace. This would have the added benefit of working even with JS private fields (#_fns), and because it's protected will only be accessible to Namespace or it's subclasses.
Let me know if one of those options sounds good to you and you want me to make a PR.
The text was updated successfully, but these errors were encountered:
Describe the bug
I have a package that exports a function requiring a typed Namespace to do its thing. This Namespace has a
SocketData
value of{ connectionId: string }
. In my actual application I then set up such a namespace to provide said setup function with a such a namespace.However, because the actual namespace I set up has additional properties in
SocketData
, for other parts of the application. This results in a TypeScript compiler error when trying to apply said actual namespace to that setup function.The error stems from the fact that the
_fns
property onNamespace
is contravariant overSocketData
.To Reproduce
Server
Additional context
In theory, because
_fns
is not markedprivate
, I can do something like this:To the TypeScript compiler,
BAD CALL
looks fine.nsB._fns
contains functions that require a Socket withSocketData
of{ foo: string }
.nsC.on('connection', ...)
provides just such a Socket. However, that middleware will try to callsocket.data.bar.toUpperCase()
and produce aTypeError: bar is undefined
. That is why the compiler warns me atBAD ASSIGNMENT
.HOWEVER, I should never actually know anything about
_fns
, even less access it. That would make it impossible to do any such mischief. Then the assignment atBAD ASSIGNMENT
would be safe. As even in that case, code like this would be sound:I took a quick glance at the Namespace source and it looks like you're keeping it public (with a
/** @private */
JSDoc comment) because you need to access it inParentNamespace.createChild
. Two suggestions to deal with that after making_fns
private:namespace['_fns'] = this._fns.slice()
increateChild
protected static setFns(ns: Namespace, fns: Array<...>) { ns._fns = fns; }
inNamespace
. This would have the added benefit of working even with JS private fields (#_fns
), and because it'sprotected
will only be accessible toNamespace
or it's subclasses.Let me know if one of those options sounds good to you and you want me to make a PR.
The text was updated successfully, but these errors were encountered: