-
Notifications
You must be signed in to change notification settings - Fork 132
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
Symbol missing from generated types #306
Comments
The const personWithExtra = {
name: 'Foo',
id: 123n,
years: 123,
extra: 'bar' // not in interface
};
function onlyPerson(input: Person) {}
onlyPerson(personWithExtra); // no errors There is a helpful type-guard That function and the symbol should probably be documented in the manual as there doesn't seem to be any mention of it. Perhaps because it is still considered experimental: protobuf-ts/packages/runtime/src/message-type-contract.ts Lines 6 to 12 in 55a7d55
|
Thanks for the summary, @jcready. We have two symbol properties, the other one is "protobuf-ts/unknown":
It is used to store unknown fields during deserialization in the message instance. That way, old code can work on a message with added fields, and not lose any data during serialization. It is a core feature of Protobuf. If an implementation doesn't support that, it's not conformant. Because of that, it seemed pretty harmless to add the experimental @cortopy, how did you run into the property? Was it through unit tests? |
I found about the Even though Symbols are not enumerable, I would still prefer as much type correctness as possible. This was one of those nasty bugs that are difficult to pin down and so I had to write some logic to remove the symbol before putting in store. This wasn't enough to fix the bug and it may be the case that I didn't need the logic, but having "hidden" properties, whether enumerable or not, is not something I was expecting |
@timostamm I wonder if we could avoid the whole mess of extra properties on the objects by using a // runtime/src/message-type-contract.ts
const MessageTypeMap = new WeakMap<object, IMessageType<any>>();
export const getMessageType = <T extends object>(ref: T): IMessageType<T> | undefined => MessageTypeMap.get(ref);
// runtime/src/binary-format-contract.ts
const UnknownFieldMap = new WeakMap<object, UnknownField[]>();
export const getUnknownFields = (ref: object): UnknownField[] | undefined => UnknownFieldMap.get(ref); // Usage
import { getMessageType, getUnknownFields, IMessageType, UnknownField } from '@protobuf-ts/runtime';
const MessageType: IMessageType<typeof someReference> | undefined = getMessageType(someReference);
if (MessageType) {
console.log(MessageType.toJson(someReference));
}
const UnknownFields: UnknownField[] | undefined = getUnknownFields(someReference);
if (UnknownFields) {
console.log(UnknownFields.map((uf) => uf.no));
} |
Ah, WeakMap is definitely a nice idea. I've been using it back in the days in ActionScript and didn't realize JavaScript implementations finally caught up :) Can't think of any drawbacks. We export both symbols, so it is a breaking change, but it seems worth it for a new major. |
This would be useful for users of SvelteKit as well. The default way of passing data from client/server around in that framework gets tripped up on these symbols and requires you clone new objects without the symbols to work. |
FWIW my suggested solution using If we wish to replace the usage of Though that then begs the question: if one were to |
I also want to point out that stamping the message with the globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
To be fair, using the
|
So far I've only found one way to maintain the current symbol stamp (and all its enumerability behaviors) while avoiding the slow down: define an EMPTY class for creating message instances and then define the symbol property on that class's prototype. Inside the base this.MessageInstance = class MessageInstance{};
globalThis.Object.defineProperty(this.MessageInstance.prototype, MESSAGE_TYPE, { value: this }); Then the const message = { repeatedField: [] };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); with const message = new this.MessageInstance();
message.repeatedField = [];
// alternatively
const message = Object.assign(new this.MessageInstance(), { repeatedField: [] }); Even though the I still think the Previous proposal: // somewhere in @protobuf-ts/runtime
const MessageTypeMap = new WeakMap<object, IMessageType<any>>();
export const setMessageType = <T extends object>(ref: T, MessageType: IMessageType<T>): void => {
MessageTypeMap.set(ref, MessageType);
};
export const getMessageType = <T extends object>(ref: T): IMessageType<T> | undefined =>
MessageTypeMap.get(ref);
// generated file, Foo$Type extends MessageType<Foo>'s create() method:
const message = {};
setMessageType(message, this); New proposal: // somewhere in @protobuf-ts/runtime
const MessageTypeMap = new WeakMap<object, IMessageType<any>>();
export const setMessageType = <T extends object>(ref: T, MessageType: IMessageType<T>): void => {
MessageTypeMap.set(ref.__proto__, MessageType);
};
export const getMessageType = <T extends object>(ref: T): IMessageType<T> | undefined =>
MessageTypeMap.get(ref.__proto__);
// generated file, Foo$Type extends MessageType<Foo>'s create() method:
const message = new this.MessageInstance();
setMessageType(message, this); |
Codegen will create typescript definitions that do not include the MESSAGE_TYPE symbol, even though it will be present in all response messages.
Following the example in the manual for this definition:
I would expect that the typescript interface is something like:
The text was updated successfully, but these errors were encountered: