diff --git a/src/core/proto/NapProto.ts b/src/core/proto/NapProto.ts new file mode 100644 index 00000000..94f1082d --- /dev/null +++ b/src/core/proto/NapProto.ts @@ -0,0 +1,139 @@ +import {MessageType, RepeatType, ScalarType} from '@protobuf-ts/runtime'; +import {PartialFieldInfo} from "@protobuf-ts/runtime/build/types/reflection-info"; + +type LowerCamelCase = CamelCaseHelper; + +type CamelCaseHelper< + S extends string, + CapNext extends boolean, + IsFirstChar extends boolean +> = S extends `${infer F}${infer R}` + ? F extends '_' + ? CamelCaseHelper + : F extends `${number}` + ? `${F}${CamelCaseHelper}` + : CapNext extends true + ? `${Uppercase}${CamelCaseHelper}` + : IsFirstChar extends true + ? `${Lowercase}${CamelCaseHelper}` + : `${F}${CamelCaseHelper}` + : ''; + +type ScalarTypeToTsType = + T extends ScalarType.DOUBLE | ScalarType.FLOAT | ScalarType.INT32 | ScalarType.FIXED32 | ScalarType.UINT32 | ScalarType.SFIXED32 | ScalarType.SINT32 ? number : + T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 ? bigint : + T extends ScalarType.BOOL ? boolean : + T extends ScalarType.STRING ? string : + T extends ScalarType.BYTES ? Uint8Array : + never; + +interface BaseProtoFieldType { + kind: 'scalar' | 'message'; + no: number; + type: T; + optional: O; + repeated: R; +} + +interface ScalarProtoFieldType extends BaseProtoFieldType { + kind: 'scalar'; +} + +interface MessageProtoFieldType ProtoMessageType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType { + kind: 'message'; +} + +type ProtoFieldType = + | ScalarProtoFieldType + | MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>; + +type ProtoMessageType = { + [key: string]: ProtoFieldType; +}; + +function ProtoField(no: number, type: T, repeated?: R, optional?: O): ScalarProtoFieldType; +function ProtoField ProtoMessageType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, repeated?: R, optional?: O): MessageProtoFieldType; +function ProtoField(no: number, type: ScalarType | (() => ProtoMessageType), repeated?: boolean, optional?: boolean): ProtoFieldType { + if (typeof type === 'function') { + return {kind: 'message', no: no, type: type, repeated: repeated ?? false, optional: optional ?? false}; + } else { + return {kind: 'scalar', no: no, type: type, repeated: repeated ?? false, optional: optional ?? false}; + } +} + +type ProtoFieldReturnType = T extends ScalarProtoFieldType + ? ScalarTypeToTsType + : T extends MessageProtoFieldType + ? ProtoStructType> + : never; + +type RequiredFieldsType = { + [K in keyof T as T[K] extends { + optional: true + } | MessageProtoFieldType ? never : LowerCamelCase] + : T[K] extends { repeated: true } + ? ProtoFieldReturnType[] + : ProtoFieldReturnType +}; + +type OptionalFieldsType = { + [K in keyof T as T[K] extends { + optional: true + } | MessageProtoFieldType ? LowerCamelCase : never]?: + T[K] extends { repeated: true } + ? ProtoFieldReturnType[] + : ProtoFieldReturnType +}; + +type ProtoStructType = RequiredFieldsType & OptionalFieldsType; + +const NapProtoMsgCache = new Map>>(); + +class NapProtoMsg { + private readonly _msg: T; + private readonly _field: PartialFieldInfo[]; + private readonly _proto_msg: MessageType>; + + constructor(fields: T) { + this._msg = fields; + this._field = Object.keys(fields).map(key => { + const field = fields[key]; + if (field.kind === 'scalar') { + return { + no: field.no, + name: key, + kind: 'scalar', + T: field.type, + opt: field.optional, + repeat: field.repeated + ? [ScalarType.STRING, ScalarType.BYTES].includes(field.type) + ? RepeatType.UNPACKED + : RepeatType.PACKED + : RepeatType.NO, + }; + } else if (field.kind === 'message') { + const rt = NapProtoMsgCache.get(field.type()) ?? (() => { + const msg = new NapProtoMsg(field.type()); + NapProtoMsgCache.set(field.type(), msg._proto_msg); + return msg._proto_msg; + })(); + return { + no: field.no, + name: key, + kind: 'message', + repeat: field.repeated ? RepeatType.PACKED : RepeatType.NO, + T: () => rt, + }; + } + }) as PartialFieldInfo[]; + this._proto_msg = new MessageType>('nya', this._field); + } + + encode(data: ProtoStructType): Uint8Array { + return this._proto_msg.toBinary(data); + } + + decode(data: Uint8Array): ProtoStructType { + return this._proto_msg.fromBinary(data); + } +}