Closed sam-step closed 2 months ago
Hi @sam-step ! Apologies for the late reply, but the explicit annotations sounds worth trying!
I was thinking it will blow up the *.ts
file output size, but it's just a type, so would get minimized out in terms of actual *.js
code.
Do you think doing an interface like const FileDescriptProto: SomeGenericType<FileDescriptorProto>
would also work?
Even if SomeGenericType
itself was included in the generated output (instead of being imported from an npm library), seems like that would also cut down on the output size quite a bit.
If you're still interested, would be great to have a PR! Thanks!
Do you think doing an interface like const FileDescriptProto: SomeGenericType
would also work?
Yes, I like this idea a lot! Addresses the compilation issue and has the nice side benefit of a much smaller (and more readable) declarations file.
Took a closer look at the code and I think this should be doable. A few questions came up that I want to get feedback on before investing more time here:
Naming
These static object have the same name as their respective messages, so initially Message
sounded right. But it feels misleading. It's not really a message, but rather a companion object of static methods, mostly encoding & decoding utilities. StaticMessage
? MessageUtilities
?
Flag or default? I'd imaging making this behavior default and avoiding yet another flag (and series of conditionals in the code) would be desired. Assuming it doesn't actually change the semantics of the generated code. But lmk if you think it'd be better to put it behind a flag.
Constant members
With outputTypeAnnotations
and outputTypeRegistry
flags, the static object has a const $type
property. Defining it as a string
in the interface would result in reduced type specificity, so I think if either of these flags are set, we'd have to further parameterize the interface, like:
interface StaticMessage<T, N extends string> {
readonly $type: N
encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T
// Others...
}
export const FileDescriptorProto: StaticMessage<FileDescriptorProto, 'google.protobuf.FileDescriptorProto'> = {
$type: "google.protobuf.FileDescriptorProto" as const,
// ...
}
const type = FileDescriptorProto.$type // Guaranteed to be "google.protobuf.FileDescriptorProto"
Does that make sense? I think this is the only way to retain the same level of specificity here, but I might be missing something.
Conditional methods
If outputExtensions
is set, the setExtension
and getExtension
methods are conditionally added to the static object depending on whether the target message has any extensions defined or not. So a single interface per build/file won't necessarily work.
Assuming these are the only two conditionally added methods, one option could be just create a second interface and union them when needed. E.g.:
interface StaticMessage<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T
// Others...
}
interface Extendable<T> {
getExtension<V>(message: T, extension: Extension<V>): V | undefined
setExtension<V>(message: T, extension: Extension<V>, value: V): void
}
const MyMessage: StaticMessage<MyMessage> & Extendable<MyMessage> = {
// ...
}
const MyOtherMessageWithoutExtensions: StaticMessage<MyOtherMessageWithoutExtensions> = {
// ...
}
Another route could be to just not conditionally add methods to the object (ie always add get/setExtension methods if outputExtensions is true, regardless of whether the message has any extensions). But it seems wasteful to add methods that'll never get called.
Naming
Good question, maybe MessageProto
or MessageConst
or MessageMethods
or MessageFns
. Maybe MessageFns
?
Flag or default
I'm good with default, since it's "just types" and should get stripped out of any bundled code and not affect output size.
constant members
Good catch on the $type
and yep makes sense it can be a generic--should work.
conditional methods
Another great catch--the StaticMessage<MyMessage> & Extendable<MyMessage>
seems pretty nice to me, if that works out.
Thanks for all of the research into this so far! You've found several things I would not thought of until getting ~1/2th way through the PR. :-)
:tada: This issue has been resolved in version 2.2.0 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
@stephenh can we expect this version to show up on the buf registry some time soon? the latest there is still at v1.178.0 🙏
Hi @novascreen ! ts-proto used to push its own artifacts to the Buf registry, but as of ~ a year ago, I believe Buf controls all publishing of the community plugins like ts-proto.
@timosaikkonen does that sound right? Is there anything you can poke at, to get the latest versions pulling back into the Buf registry?
Thanks!
Hi, I recently ran into the "inferred type of this node exceeds the maximum length" error referenced in https://github.com/stephenh/ts-proto/issues/449
It can be easily reproduced by running ts-proto on google/protobuf/descriptor.proto.
I understand turning off exact types works around the problem. However, the exact types feature is pretty nice so I'm looking to see if there are alternative solutions.
Following the compiler's suggestion to add explicit annotations seems to address the problem. E.g.
Is adding support for these explicit annotations something you'd consider (it certainly produces more verbose code...)? If so, happy to take a stab at contributing a PR for it.