diff --git a/Cargo.lock b/Cargo.lock index 2c491dc46..148215565 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,6 +380,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad1fa75f77bbd06f187540aa1d70ca50b80b27ce85e7f41c0ce7ff42b34ed3b" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1cef5dd4a4457dd11529e743d18ba4fabbd5f20b6895f4c865cb257337dcf9f" +dependencies = [ + "virtue", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -957,6 +977,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "itertools" version = "0.10.5" @@ -1692,10 +1718,11 @@ dependencies = [ "alloy-sol-types", "anyhow", "bcs", - "bincode", + "bincode 1.3.3", "heck 0.3.3", "hex", "include_dir", + "indoc", "lazy_static", "maplit", "phf", @@ -1737,7 +1764,7 @@ dependencies = [ name = "serde-reflection" version = "0.5.0" dependencies = [ - "bincode", + "bincode 1.3.3", "erased-discriminant", "once_cell", "serde", @@ -1770,12 +1797,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.7.0", "itoa", + "memchr", "ryu", "serde", ] @@ -2048,6 +2076,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "ts-generator" +version = "0.1.0" +dependencies = [ + "bincode 2.0.0", + "serde", + "serde-generate", + "serde-reflection", + "serde_json", +] + [[package]] name = "typeid" version = "1.0.2" @@ -2108,6 +2147,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unty" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a88342087869553c259588a3ec9ca73ce9b2d538b7051ba5789ff236b6c129" + [[package]] name = "valuable" version = "0.1.0" @@ -2126,6 +2171,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 18b6e7cda..8d9997d15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "serde-reflection", "serde-generate", "serde-generate-bin", + "suite/*" ] resolver = "2" diff --git a/rustfmt.toml b/rustfmt.toml index 80eeb4054..d6a77951c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,3 @@ edition = "2018" use_field_init_shorthand = true +disable_all_formatting = true \ No newline at end of file diff --git a/serde-generate/Cargo.toml b/serde-generate/Cargo.toml index 9c2509fbc..910d347ee 100644 --- a/serde-generate/Cargo.toml +++ b/serde-generate/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1.0.126", features = ["derive"] } textwrap = "0.13.4" phf = { version = "0.10", features = ["macros"], optional = true } serde-reflection = { path = "../serde-reflection", version = "0.5.0" } +indoc = { version = "2" } [dev-dependencies] alloy-sol-types = "0.8.18" diff --git a/serde-generate/runtime/typescript/bincode.ts b/serde-generate/runtime/typescript/bincode.ts new file mode 100644 index 000000000..d3194edbc --- /dev/null +++ b/serde-generate/runtime/typescript/bincode.ts @@ -0,0 +1,25 @@ +import { BinaryReader, BinaryWriter } from "./serde.ts"; + +export class BincodeReader extends BinaryReader { + read_length() { + return Number(this.read_u64()) + } + public read_variant_index() { + return this.read_u32() + } + check_that_key_slices_are_increasing(key1: [number, number], key2: [number, number]) { + return + } +} + +export class BincodeWriter extends BinaryWriter { + write_length(value: number) { + this.write_u64(value) + } + public write_variant_index(value: number) { + this.write_u32(value) + } + public sort_map_entries(offsets: number[]) { + return + } +} diff --git a/serde-generate/runtime/typescript/bincode/bincodeDeserializer.ts b/serde-generate/runtime/typescript/bincode/bincodeDeserializer.ts deleted file mode 100644 index f8de97ed8..000000000 --- a/serde-generate/runtime/typescript/bincode/bincodeDeserializer.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -import { BinaryDeserializer } from "../serde/binaryDeserializer.ts"; - -export class BincodeDeserializer extends BinaryDeserializer { - deserializeLen(): number { - return Number(this.deserializeU64()); - } - - public deserializeVariantIndex(): number { - return this.deserializeU32(); - } - - checkThatKeySlicesAreIncreasing( - key1: [number, number], - key2: [number, number], - ): void { - return; - } -} diff --git a/serde-generate/runtime/typescript/bincode/bincodeSerializer.ts b/serde-generate/runtime/typescript/bincode/bincodeSerializer.ts deleted file mode 100644 index dce23a19f..000000000 --- a/serde-generate/runtime/typescript/bincode/bincodeSerializer.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -import { BinarySerializer } from "../serde/binarySerializer.ts"; - -export class BincodeSerializer extends BinarySerializer { - serializeLen(value: number): void { - this.serializeU64(value); - } - - public serializeVariantIndex(value: number): void { - this.serializeU32(value); - } - - public sortMapEntries(offsets: number[]): void { - return; - } -} diff --git a/serde-generate/runtime/typescript/bincode/mod.ts b/serde-generate/runtime/typescript/bincode/mod.ts deleted file mode 100644 index 302340333..000000000 --- a/serde-generate/runtime/typescript/bincode/mod.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -export { BincodeSerializer } from "./bincodeSerializer.ts"; -export { BincodeDeserializer } from "./bincodeDeserializer.ts"; diff --git a/serde-generate/runtime/typescript/serde.ts b/serde-generate/runtime/typescript/serde.ts new file mode 100644 index 000000000..217818b19 --- /dev/null +++ b/serde-generate/runtime/typescript/serde.ts @@ -0,0 +1,367 @@ +export type Optional = T | null +export type Seq = T[] +export type Tuple = { + [K in keyof T as `$${Exclude extends string ? Exclude : never}`]: T[K] +} +export type ListTuple = Tuple[] +export type Map = globalThis.Map + +export type unit = null +export type bool = boolean +export type i8 = number +export type i16 = number +export type i32 = number +export type i64 = bigint +export type i128 = bigint +export type u8 = number +export type u16 = number +export type u32 = number +export type u64 = bigint +export type u128 = bigint +export type f32 = number +export type f64 = number +export type char = string +export type str = string +export type bytes = Uint8Array + +export interface Reader { + read_string(): string + read_bool(): boolean + read_unit(): null + read_char(): string + read_f32(): number + read_f64(): number + read_u8(): number + read_u16(): number + read_u32(): number + read_u64(): bigint + read_u128(): bigint + read_i8(): number + read_i16(): number + read_i32(): number + read_i64(): bigint + read_i128(): bigint + read_length(): number + read_variant_index(): number + read_option_tag(): boolean + read_list(read_fn: () => T, length?: number): T[] + read_map(read_key: () => K, read_value: () => V): Map + check_that_key_slices_are_increasing(key1: [number, number], key2: [number, number]): void +} + +export interface Writer { + write_string(value: string): void + write_bool(value: boolean): void + write_unit(value: null): void + write_char(value: string): void + write_f32(value: number): void + write_f64(value: number): void + write_u8(value: number): void + write_u16(value: number): void + write_u32(value: number): void + write_u64(value: bigint | number): void + write_u128(value: bigint | number): void + write_i8(value: number): void + write_i16(value: number): void + write_i32(value: number): void + write_i64(value: bigint | number): void + write_i128(value: bigint | number): void + write_length(value: number): void + write_variant_index(value: number): void + write_option_tag(value: boolean): void + write_map(value: Map, write_key: (key: K) => void, write_value: (value: V) => void): void + get_bytes(): Uint8Array + sort_map_entries(offsets: number[]): void +} + +const BIG_32 = 32n +const BIG_64 = 64n +const BIG_32Fs = 429967295n +const BIG_64Fs = 18446744073709551615n + +let WRITE_HEAP = new DataView(new ArrayBuffer(128)) + +export abstract class BinaryWriter implements Writer { + public static readonly TEXT_ENCODER = new TextEncoder() + + public view = WRITE_HEAP + public offset = 0 + + constructor() { + if (WRITE_HEAP.byteLength > 1024) { + this.view = WRITE_HEAP = new DataView(new ArrayBuffer(128)) + } + } + +private alloc(allocLength: number) { + const wish_size = this.offset + allocLength + + const current_length = this.view.buffer.byteLength + if (wish_size > current_length) { + let new_buffer_length = current_length + while (new_buffer_length < wish_size) new_buffer_length = new_buffer_length << 1 + + // TODO: there is new API for resizing buffer, but in Node it seems to be slower then allocating new + // this.buffer.resize(newBufferLength) + + const newBuffer = new Uint8Array(new_buffer_length) + newBuffer.set(new Uint8Array(this.view.buffer)) + + this.view = WRITE_HEAP = new DataView(newBuffer.buffer) + } +} + + abstract write_length(value: number): void + abstract write_variant_index(value: number): void + abstract sort_map_entries(offsets: number[]): void + + // https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto#buffer_sizing + public write_string(value: string) { + // allocate space for string length marker and whole string + this.alloc(8 + value.length * 3) + + // encode into buffer with space for string length (u64) + let { written: length } = BinaryWriter.TEXT_ENCODER.encodeInto(value, new Uint8Array(this.view.buffer, this.offset + 8)) + + const b_length = BigInt(length) + this.view.setBigUint64(this.offset, b_length, true) + this.offset += (8 + length) + } + + public write_bool(value: boolean) { + this.write_u8(value ? 1 : 0) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/explicit-module-boundary-types + public write_unit(_value: null) { + return + } + + public write_u8(value: number) { + this.alloc(1) + this.view.setUint8(this.offset, value) + this.offset += 1 + } + + public write_u16(value: number) { + this.alloc(2) + this.view.setUint16(this.offset, value, true) + this.offset += 2 + } + + public write_u32(value: number) { + this.alloc(4) + this.view.setUint32(this.offset, value, true) + this.offset += 4 + } + + public write_u64(value: bigint | number) { + this.alloc(8) + this.view.setBigUint64(this.offset, BigInt(value), true) + this.offset += 8 + } + + public write_u128(value: bigint | number) { + const low = BigInt(value) & BIG_64Fs, high = BigInt(value) >> BIG_64 + + // write little endian number + this.write_u64(low) + this.write_u64(high) + } + + public write_i8(value: number) { + this.alloc(1) + this.view.setInt8(this.offset, value) + this.offset += 1 + } + + public write_i16(value: number) { + this.alloc(2) + this.view.setInt16(this.offset, value, true) + this.offset += 2 + } + + public write_i32(value: number) { + this.alloc(4) + this.view.setInt32(this.offset, value, true) + this.offset += 4 + } + + public write_i64(value: bigint | number) { + this.alloc(8) + this.view.setBigInt64(this.offset, BigInt(value), true) + this.offset += 8 + } + + public write_i128(value: bigint | number) { + const low = BigInt(value) & BIG_64Fs, high = BigInt(value) >> BIG_64 + + // write little endian number + this.write_i64(low) + this.write_i64(high) + } + + public write_option_tag(value: boolean) { + this.write_bool(value) + } + + public write_map(map: Map, write_key: (key: T) => void, write_value: (value: V) => void): void { + this.write_length(map.size) + const offsets: number[] = [] + for (const [k, v] of map.entries()) { + offsets.push(this.offset) + write_key(k) + write_value(v) + } + this.sort_map_entries(offsets) + } + + public write_f32(value: number) { + this.alloc(4) + this.view.setFloat32(this.offset, value, true) + this.offset += 4 + } + + public write_f64(value: number) { + this.alloc(8) + this.view.setFloat64(this.offset, value, true) + this.offset += 8 + } + + public write_char(_value: string) { + throw new Error("Method serializeChar not implemented.") + } + + public get_bytes() { + return new Uint8Array(this.view.buffer).subarray(0, this.offset) + } +} + +export abstract class BinaryReader implements Reader { + private static readonly TEXT_DECODER = new TextDecoder() + + public offset = 0 + public view: DataView + + constructor(data: Uint8Array) { + this.view = new DataView(data.buffer) + } + + abstract read_length(): number + abstract read_variant_index(): number + abstract check_that_key_slices_are_increasing(key1: [number, number], key2: [number, number]): void + + public read_string() { + const length = this.read_length() + const decoded = BinaryReader.TEXT_DECODER.decode(new Uint8Array(this.view.buffer, this.offset, length)) + this.offset += length + return decoded + } + + public read_bool() { + return this.read_u8() === 1 + } + + public read_unit() { + return null + } + + public read_u8() { + const value = this.view.getUint8(this.offset) + this.offset += 1 + return value + } + + public read_u16() { + const value = this.view.getUint16(this.offset, true) + this.offset += 2 + return value + } + + public read_u32() { + const value = this.view.getUint32(this.offset, true) + this.offset += 4 + return value + } + + public read_u64() { + const value = this.view.getBigUint64(this.offset, true) + this.offset += 8 + return value + } + + public read_u128() { + const low = this.read_u64(), high = this.read_u64() + // combine the two 64-bit values and return (little endian) + return (high << BIG_64) | low + } + + public read_i8() { + const value = this.view.getInt8(this.offset) + this.offset += 1 + return value + } + + public read_i16() { + const value = this.view.getInt16(this.offset, true) + this.offset += 2 + return value + } + + public read_i32() { + const value = this.view.getInt32(this.offset, true) + this.offset += 4 + return value + } + + public read_i64() { + const value = this.view.getBigInt64(this.offset, true) + this.offset += 8 + return value + } + + public read_i128() { + const low = this.read_i64(), high = this.read_i64() + // combine the two 64-bit values and return (little endian) + return (high << BIG_64) | low + } + + public read_option_tag = this.read_bool + + public read_list(read_fn: () => T, listLength?: number) { + const length = listLength ?? this.read_length(), list = new Array(length) + for (let i = 0; i < length; i++) list[i] = read_fn() + return list + } + + public read_map(read_key: () => K, read_value: () => V) { + const length = this.read_length(), obj = new Map() + let previousKeyStart = 0, previousKeyEnd = 0 + for (let i = 0; i < length; i++) { + const keyStart = this.offset, key = read_key(), keyEnd = this.offset + if (i > 0) { + this.check_that_key_slices_are_increasing([previousKeyStart, previousKeyEnd], [keyStart, keyEnd]) + } + previousKeyStart = keyStart + previousKeyEnd = keyEnd + obj.set(key, read_value()) + } + return obj + } + + public read_char(): string { + throw new Error("Method read_char not implemented.") + } + + public read_f32() { + const value = this.view.getFloat32(this.offset, true) + this.offset += 4 + return value + } + + public read_f64() { + const value = this.view.getFloat64(this.offset, true) + this.offset += 8 + return value + } +} diff --git a/serde-generate/runtime/typescript/serde/binaryDeserializer.ts b/serde-generate/runtime/typescript/serde/binaryDeserializer.ts deleted file mode 100644 index a9a1183ae..000000000 --- a/serde-generate/runtime/typescript/serde/binaryDeserializer.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -import { Deserializer } from "./deserializer.ts"; -import * as util from "https://deno.land/std@0.85.0/node/util.ts"; - -export abstract class BinaryDeserializer implements Deserializer { - private static readonly BIG_32: bigint = BigInt(32); - private static readonly BIG_64: bigint = BigInt(64); - private static readonly textDecoder = typeof window === "undefined" - ? new util.TextDecoder() - : new TextDecoder(); - public buffer: ArrayBuffer; - public offset: number; - - constructor(data: Uint8Array) { - // copies data to prevent outside mutation of buffer. - this.buffer = new ArrayBuffer(data.length); - new Uint8Array(this.buffer).set(data, 0); - this.offset = 0; - } - - private read(length: number): ArrayBuffer { - const bytes = this.buffer.slice(this.offset, this.offset + length); - this.offset += length; - return bytes; - } - - abstract deserializeLen(): number; - - abstract deserializeVariantIndex(): number; - - abstract checkThatKeySlicesAreIncreasing( - key1: [number, number], - key2: [number, number], - ): void; - - public deserializeStr(): string { - const value = this.deserializeBytes(); - return BinaryDeserializer.textDecoder.decode(value); - } - - public deserializeBytes(): Uint8Array { - const len = this.deserializeLen(); - if (len < 0) { - throw new Error("Length of a bytes array can't be negative"); - } - return new Uint8Array(this.read(len)); - } - - public deserializeBool(): boolean { - const bool = new Uint8Array(this.read(1))[0]; - return bool == 1; - } - - public deserializeUnit(): null { - return null; - } - - public deserializeU8(): number { - return new DataView(this.read(1)).getUint8(0); - } - - public deserializeU16(): number { - return new DataView(this.read(2)).getUint16(0, true); - } - - public deserializeU32(): number { - return new DataView(this.read(4)).getUint32(0, true); - } - - public deserializeU64(): bigint { - const low = this.deserializeU32(); - const high = this.deserializeU32(); - - // combine the two 32-bit values and return (little endian) - return BigInt( - (BigInt(high.toString()) << BinaryDeserializer.BIG_32) | - BigInt(low.toString()), - ); - } - - public deserializeU128(): bigint { - const low = this.deserializeU64(); - const high = this.deserializeU64(); - - // combine the two 64-bit values and return (little endian) - return BigInt( - (BigInt(high.toString()) << BinaryDeserializer.BIG_64) | - BigInt(low.toString()), - ); - } - - public deserializeI8(): number { - return new DataView(this.read(1)).getInt8(0); - } - - public deserializeI16(): number { - return new DataView(this.read(2)).getInt16(0, true); - } - - public deserializeI32(): number { - return new DataView(this.read(4)).getInt32(0, true); - } - - public deserializeI64(): bigint { - const low = this.deserializeI32(); - const high = this.deserializeI32(); - - // combine the two 32-bit values and return (little endian) - return (BigInt(high.toString()) << BinaryDeserializer.BIG_32) | - BigInt(low.toString()); - } - - public deserializeI128(): bigint { - const low = this.deserializeI64(); - const high = this.deserializeI64(); - - // combine the two 64-bit values and return (little endian) - return (BigInt(high.toString()) << BinaryDeserializer.BIG_64) | - BigInt(low.toString()); - } - - public deserializeOptionTag(): boolean { - return this.deserializeBool(); - } - - public getBufferOffset(): number { - return this.offset; - } - - public deserializeChar(): string { - throw new Error("Method deserializeChar not implemented."); - } - - public deserializeF32(): number { - return new DataView(this.read(4)).getFloat32(0, true); - } - - public deserializeF64(): number { - return new DataView(this.read(8)).getFloat64(0, true); - } -} diff --git a/serde-generate/runtime/typescript/serde/binarySerializer.ts b/serde-generate/runtime/typescript/serde/binarySerializer.ts deleted file mode 100644 index 55d244f44..000000000 --- a/serde-generate/runtime/typescript/serde/binarySerializer.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -import { Serializer } from "./serializer.ts"; -import * as util from "https://deno.land/std@0.85.0/node/util.ts"; - -export abstract class BinarySerializer implements Serializer { - private static readonly BIG_32: bigint = BigInt(32); - private static readonly BIG_64: bigint = BigInt(64); - - private static readonly BIG_32Fs: bigint = BigInt("4294967295"); - private static readonly BIG_64Fs: bigint = BigInt("18446744073709551615"); - - private static readonly textEncoder = typeof window === "undefined" - ? new util.TextEncoder() - : new TextEncoder(); - - private buffer: ArrayBuffer; - private offset: number; - - constructor() { - this.buffer = new ArrayBuffer(64); - this.offset = 0; - } - - private ensureBufferWillHandleSize(bytes: number) { - while (this.buffer.byteLength < this.offset + bytes) { - const newBuffer = new ArrayBuffer(this.buffer.byteLength * 2); - new Uint8Array(newBuffer).set(new Uint8Array(this.buffer)); - this.buffer = newBuffer; - } - } - - protected serialize(values: Uint8Array) { - this.ensureBufferWillHandleSize(values.length); - new Uint8Array(this.buffer, this.offset).set(values); - this.offset += values.length; - } - - abstract serializeLen(value: number): void; - - abstract serializeVariantIndex(value: number): void; - - abstract sortMapEntries(offsets: number[]): void; - - public serializeStr(value: string): void { - this.serializeBytes(BinarySerializer.textEncoder.encode(value)); - } - - public serializeBytes(value: Uint8Array): void { - this.serializeLen(value.length); - this.serialize(value); - } - - public serializeBool(value: boolean): void { - const byteValue = value ? 1 : 0; - this.serialize(new Uint8Array([byteValue])); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/explicit-module-boundary-types - public serializeUnit(_value: null): void { - return; - } - - private serializeWithFunction( - fn: (byteOffset: number, value: number, littleEndian: boolean) => void, - bytesLength: number, - value: number, - ) { - this.ensureBufferWillHandleSize(bytesLength); - const dv = new DataView(this.buffer, this.offset); - fn.apply(dv, [0, value, true]); - this.offset += bytesLength; - } - - public serializeU8(value: number): void { - this.serialize(new Uint8Array([value])); - } - - public serializeU16(value: number): void { - this.serializeWithFunction(DataView.prototype.setUint16, 2, value); - } - - public serializeU32(value: number): void { - this.serializeWithFunction(DataView.prototype.setUint32, 4, value); - } - - public serializeU64(value: BigInt | number): void { - const low = BigInt(value.toString()) & BinarySerializer.BIG_32Fs; - const high = BigInt(value.toString()) >> BinarySerializer.BIG_32; - - // write little endian number - this.serializeU32(Number(low)); - this.serializeU32(Number(high)); - } - - public serializeU128(value: BigInt | number): void { - const low = BigInt(value.toString()) & BinarySerializer.BIG_64Fs; - const high = BigInt(value.toString()) >> BinarySerializer.BIG_64; - - // write little endian number - this.serializeU64(low); - this.serializeU64(high); - } - - public serializeI8(value: number): void { - const bytes = 1; - this.ensureBufferWillHandleSize(bytes); - new DataView(this.buffer, this.offset).setInt8(0, value); - this.offset += bytes; - } - - public serializeI16(value: number): void { - const bytes = 2; - this.ensureBufferWillHandleSize(bytes); - new DataView(this.buffer, this.offset).setInt16(0, value, true); - this.offset += bytes; - } - - public serializeI32(value: number): void { - const bytes = 4; - this.ensureBufferWillHandleSize(bytes); - new DataView(this.buffer, this.offset).setInt32(0, value, true); - this.offset += bytes; - } - - public serializeI64(value: bigint | number): void { - const low = BigInt(value) & BinarySerializer.BIG_32Fs; - const high = BigInt(value) >> BinarySerializer.BIG_32; - - // write little endian number - this.serializeI32(Number(low)); - this.serializeI32(Number(high)); - } - - public serializeI128(value: bigint | number): void { - const low = BigInt(value) & BinarySerializer.BIG_64Fs; - const high = BigInt(value) >> BinarySerializer.BIG_64; - - // write little endian number - this.serializeI64(low); - this.serializeI64(high); - } - - public serializeOptionTag(value: boolean): void { - this.serializeBool(value); - } - - public getBufferOffset(): number { - return this.offset; - } - - public getBytes(): Uint8Array { - return new Uint8Array(this.buffer).slice(0, this.offset); - } - - public serializeChar(_value: string): void { - throw new Error("Method serializeChar not implemented."); - } - - public serializeF32(value: number): void { - const bytes = 4; - this.ensureBufferWillHandleSize(bytes); - new DataView(this.buffer, this.offset).setFloat32(0, value, true); - this.offset += bytes; - } - - public serializeF64(value: number): void { - const bytes = 8; - this.ensureBufferWillHandleSize(bytes); - new DataView(this.buffer, this.offset).setFloat64(0, value, true); - this.offset += bytes; - } -} diff --git a/serde-generate/runtime/typescript/serde/deserializer.ts b/serde-generate/runtime/typescript/serde/deserializer.ts deleted file mode 100644 index 1cd03d094..000000000 --- a/serde-generate/runtime/typescript/serde/deserializer.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -export interface Deserializer { - deserializeStr(): string; - - deserializeBytes(): Uint8Array; - - deserializeBool(): boolean; - - deserializeUnit(): null; - - deserializeChar(): string; - - deserializeF32(): number; - - deserializeF64(): number; - - deserializeU8(): number; - - deserializeU16(): number; - - deserializeU32(): number; - - deserializeU64(): bigint; - - deserializeU128(): bigint; - - deserializeI8(): number; - - deserializeI16(): number; - - deserializeI32(): number; - - deserializeI64(): bigint; - - deserializeI128(): bigint; - - deserializeLen(): number; - - deserializeVariantIndex(): number; - - deserializeOptionTag(): boolean; - - getBufferOffset(): number; - - checkThatKeySlicesAreIncreasing( - key1: [number, number], - key2: [number, number], - ): void; -} diff --git a/serde-generate/runtime/typescript/serde/mod.ts b/serde-generate/runtime/typescript/serde/mod.ts deleted file mode 100644 index 4c8a4cf74..000000000 --- a/serde-generate/runtime/typescript/serde/mod.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -export * from "./types.ts"; -export * from "./serializer.ts"; -export * from "./deserializer.ts"; -export * from "./binarySerializer.ts"; -export * from "./binaryDeserializer.ts"; diff --git a/serde-generate/runtime/typescript/serde/serializer.ts b/serde-generate/runtime/typescript/serde/serializer.ts deleted file mode 100644 index 361e337a8..000000000 --- a/serde-generate/runtime/typescript/serde/serializer.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -export interface Serializer { - serializeStr(value: string): void; - - serializeBytes(value: Uint8Array): void; - - serializeBool(value: boolean): void; - - serializeUnit(value: null): void; - - serializeChar(value: string): void; - - serializeF32(value: number): void; - - serializeF64(value: number): void; - - serializeU8(value: number): void; - - serializeU16(value: number): void; - - serializeU32(value: number): void; - - serializeU64(value: bigint | number): void; - - serializeU128(value: bigint | number): void; - - serializeI8(value: number): void; - - serializeI16(value: number): void; - - serializeI32(value: number): void; - - serializeI64(value: bigint | number): void; - - serializeI128(value: bigint | number): void; - - serializeLen(value: number): void; - - serializeVariantIndex(value: number): void; - - serializeOptionTag(value: boolean): void; - - getBufferOffset(): number; - - getBytes(): Uint8Array; - - sortMapEntries(offsets: number[]): void; -} diff --git a/serde-generate/runtime/typescript/serde/types.ts b/serde-generate/runtime/typescript/serde/types.ts deleted file mode 100644 index ea1049b27..000000000 --- a/serde-generate/runtime/typescript/serde/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates - * SPDX-License-Identifier: MIT OR Apache-2.0 - */ - -export type Optional = T | null; -export type Seq = T[]; -export type Tuple = T; -export type ListTuple = Tuple[]; - -export type unit = null; -export type bool = boolean; -export type int8 = number; -export type int16 = number; -export type int32 = number; -export type int64 = bigint; -export type int128 = bigint; -export type uint8 = number; -export type uint16 = number; -export type uint32 = number; -export type uint64 = bigint; -export type uint128 = bigint; -export type float32 = number; -export type float64 = number; -export type char = string; -export type str = string; -export type bytes = Uint8Array; diff --git a/serde-generate/src/common.rs b/serde-generate/src/common.rs index e17bc72e8..36abf08dc 100644 --- a/serde-generate/src/common.rs +++ b/serde-generate/src/common.rs @@ -1,32 +1,29 @@ -// Copyright (c) Facebook, Inc. and its affiliates -// SPDX-License-Identifier: MIT OR Apache-2.0 - use serde_reflection::Format; pub(crate) fn mangle_type(format: &Format) -> String { use Format::*; match format { TypeName(x) => x.to_string(), - Unit => "unit".into(), - Bool => "bool".into(), - I8 => "i8".into(), - I16 => "i16".into(), - I32 => "i32".into(), - I64 => "i64".into(), - I128 => "i128".into(), - U8 => "u8".into(), - U16 => "u16".into(), - U32 => "u32".into(), - U64 => "u64".into(), - U128 => "u128".into(), - F32 => "f32".into(), - F64 => "f64".into(), - Char => "char".into(), - Str => "str".into(), - Bytes => "bytes".into(), + Unit => "unit".into(), + Bool => "bool".into(), + I8 => "i8".into(), + I16 => "i16".into(), + I32 => "i32".into(), + I64 => "i64".into(), + I128 => "i128".into(), + U8 => "u8".into(), + U16 => "u16".into(), + U32 => "u32".into(), + U64 => "u64".into(), + U128 => "u128".into(), + F32 => "f32".into(), + F64 => "f64".into(), + Char => "char".into(), + Str => "str".into(), + Bytes => "bytes".into(), - Option(format) => format!("option_{}", mangle_type(format)), - Seq(format) => format!("vector_{}", mangle_type(format)), + Option(format) => format!("option_{}", mangle_type(format)), + Seq(format) => format!("vector_{}", mangle_type(format)), Map { key, value } => format!("map_{}_to_{}", mangle_type(key), mangle_type(value)), Tuple(formats) => format!( "tuple{}_{}", diff --git a/serde-generate/src/typescript.rs b/serde-generate/src/typescript.rs index be8889d61..e69bd3f56 100644 --- a/serde-generate/src/typescript.rs +++ b/serde-generate/src/typescript.rs @@ -1,736 +1,510 @@ -// Copyright (c) Facebook, Inc. and its affiliates -// SPDX-License-Identifier: MIT OR Apache-2.0 - use crate::{ - common, - indent::{IndentConfig, IndentedWriter}, - CodeGeneratorConfig, + common, + indent::{IndentConfig, IndentedWriter}, + CodeGeneratorConfig, }; -use heck::CamelCase; +use heck::{CamelCase, SnakeCase}; use include_dir::include_dir as include_directory; +use indoc::{formatdoc, indoc, writedoc}; use serde_reflection::{ContainerFormat, Format, FormatHolder, Named, Registry, VariantFormat}; use std::{ - collections::{BTreeMap, HashMap}, - io::{Result, Write}, - path::PathBuf, + collections::{BTreeMap, HashMap}, + io::{Result, Write}, + path::PathBuf, }; -/// Main configuration object for code-generation in TypeScript, powered by -/// the Deno runtime. +/// Main configuration object for code-generation in TypeScript pub struct CodeGenerator<'a> { - /// Language-independent configuration. - config: &'a CodeGeneratorConfig, - /// Mapping from external type names to fully-qualified class names (e.g. "MyClass" -> "com.my_org.my_package.MyClass"). - /// Derived from `config.external_definitions`. - external_qualified_names: HashMap, - /// vector of namespaces to import - namespaces_to_import: Vec, + /// Language-independent configuration. + config: &'a CodeGeneratorConfig, + /// Mapping from external type names to fully-qualified class names (e.g. "MyClass" -> "com.my_org.my_package.MyClass"). + /// Derived from `config.external_definitions`. + external_qualified_names: HashMap, + /// vector of namespaces to import + namespaces_to_import: Vec, } /// Shared state for the code generation of a TypeScript source file. struct TypeScriptEmitter<'a, T> { - /// Writer. - out: IndentedWriter, - /// Generator. - generator: &'a CodeGenerator<'a>, + /// Writer. + out: IndentedWriter, + /// Generator. + generator: &'a CodeGenerator<'a>, } impl<'a> CodeGenerator<'a> { - /// Create a TypeScript code generator for the given config. - pub fn new(config: &'a CodeGeneratorConfig) -> Self { - if config.c_style_enums { - panic!("TypeScript does not support generating c-style enums"); - } - let mut external_qualified_names = HashMap::new(); - for (namespace, names) in &config.external_definitions { - for name in names { - external_qualified_names.insert( - name.to_string(), - format!("{}.{}", namespace.to_camel_case(), name), - ); - } - } - Self { - config, - external_qualified_names, - namespaces_to_import: config - .external_definitions - .keys() - .map(|k| k.to_string()) - .collect::>(), - } - } - - /// Output class definitions for `registry` in a single source file. - pub fn output(&self, out: &mut dyn Write, registry: &Registry) -> Result<()> { - let mut emitter = TypeScriptEmitter { - out: IndentedWriter::new(out, IndentConfig::Space(2)), - generator: self, - }; - - emitter.output_preamble()?; - - for (name, format) in registry { - emitter.output_container(name, format)?; - } - - if self.config.serialization { - emitter.output_helpers(registry)?; - } - - Ok(()) - } + /// Create a TypeScript code generator for the given config. + pub fn new(config: &'a CodeGeneratorConfig) -> Self { + if config.c_style_enums { + panic!("TypeScript does not support generating c-style enums"); + } + let mut external_qualified_names = HashMap::new(); + for (namespace, names) in &config.external_definitions { + for name in names { + external_qualified_names.insert( + name.to_string(), + format!("{}.{}", namespace.to_camel_case(), name), + ); + } + } + Self { + config, + external_qualified_names, + namespaces_to_import: config.external_definitions.keys().map(|k| k.to_string()).collect::>(), + } + } + + /// Output class definitions for `registry` in a single source file. + pub fn output(&self, out: &mut dyn Write, registry: &Registry) -> Result<()> { + let mut emitter = TypeScriptEmitter { + out: IndentedWriter::new(out, IndentConfig::Tab), + generator: self, + }; + + emitter.output_preamble()?; + + for (name, format) in registry { + writeln!(emitter.out)?; + emitter.generate_container_typedef(name, format)?; + } + if self.config.serialization { + for (name, format) in registry { + writeln!(emitter.out)?; + emitter.generate_container(name, format)?; + } + } + + Ok(()) + } } -impl<'a, T> TypeScriptEmitter<'a, T> -where - T: Write, -{ - fn output_preamble(&mut self) -> Result<()> { - writeln!( - self.out, - r#" -import {{ Serializer, Deserializer }} from '../serde/mod.ts'; -import {{ BcsSerializer, BcsDeserializer }} from '../bcs/mod.ts'; -import {{ Optional, Seq, Tuple, ListTuple, unit, bool, int8, int16, int32, int64, int128, uint8, uint16, uint32, uint64, uint128, float32, float64, char, str, bytes }} from '../serde/mod.ts'; -"#, - )?; - for namespace in self.generator.namespaces_to_import.iter() { - writeln!( - self.out, - "import * as {} from '../{}/mod.ts';\n", - namespace.to_camel_case(), - namespace - )?; - } - - Ok(()) - } - - fn quote_qualified_name(&self, name: &str) -> String { - self.generator - .external_qualified_names - .get(name) - .cloned() - .unwrap_or_else(|| name.to_string()) - } - - fn output_comment(&mut self, name: &str) -> std::io::Result<()> { - let path = vec![name.to_string()]; - if let Some(doc) = self.generator.config.comments.get(&path) { - let text = textwrap::indent(doc, " * ").replace("\n\n", "\n *\n"); - writeln!(self.out, "/**\n{} */", text)?; - } - Ok(()) - } - - fn quote_type(&self, format: &Format) -> String { - use Format::*; - match format { - TypeName(x) => self.quote_qualified_name(x), - Unit => "unit".into(), - Bool => "bool".into(), - I8 => "int8".into(), - I16 => "int16".into(), - I32 => "int32".into(), - I64 => "int64".into(), - I128 => "int128".into(), - U8 => "uint8".into(), - U16 => "uint16".into(), - U32 => "uint32".into(), - U64 => "uint64".into(), - U128 => "uint128".into(), - F32 => "float32".into(), - F64 => "float64".into(), - Char => "char".into(), - Str => "str".into(), - Bytes => "bytes".into(), - - Option(format) => format!("Optional<{}>", self.quote_type(format)), - Seq(format) => format!("Seq<{}>", self.quote_type(format)), - Map { key, value } => { - format!("Map<{},{}>", self.quote_type(key), self.quote_type(value)) - } - Tuple(formats) => format!("Tuple<[{}]>", self.quote_types(formats, ", ")), - TupleArray { - content, - size: _size, - } => format!("ListTuple<[{}]>", self.quote_type(content),), - Variable(_) => panic!("unexpected value"), - } - } - - fn quote_types(&self, formats: &[Format], sep: &str) -> String { - formats - .iter() - .map(|f| self.quote_type(f)) - .collect::>() - .join(sep) - } - - fn output_helpers(&mut self, registry: &Registry) -> Result<()> { - let mut subtypes = BTreeMap::new(); - for format in registry.values() { - format - .visit(&mut |f| { - if Self::needs_helper(f) { - subtypes.insert(common::mangle_type(f), f.clone()); - } - Ok(()) - }) - .unwrap(); - } - - writeln!(self.out, "export class Helpers {{")?; - self.out.indent(); - for (mangled_name, subtype) in &subtypes { - self.output_serialization_helper(mangled_name, subtype)?; - self.output_deserialization_helper(mangled_name, subtype)?; - } - self.out.unindent(); - writeln!(self.out, "}}")?; - writeln!(self.out) - } - - fn needs_helper(format: &Format) -> bool { - use Format::*; - matches!( - format, - Option(_) | Seq(_) | Map { .. } | Tuple(_) | TupleArray { .. } - ) - } - - fn quote_serialize_value(&self, value: &str, format: &Format, use_this: bool) -> String { - use Format::*; - let this_str = if use_this { "this." } else { "" }; - - match format { - TypeName(_) => format!("{}{}.serialize(serializer);", this_str, value), - Unit => format!("serializer.serializeUnit({}{});", this_str, value), - Bool => format!("serializer.serializeBool({}{});", this_str, value), - I8 => format!("serializer.serializeI8({}{});", this_str, value), - I16 => format!("serializer.serializeI16({}{});", this_str, value), - I32 => format!("serializer.serializeI32({}{});", this_str, value), - I64 => format!("serializer.serializeI64({}{});", this_str, value), - I128 => format!("serializer.serializeI128({}{});", this_str, value), - U8 => format!("serializer.serializeU8({}{});", this_str, value), - U16 => format!("serializer.serializeU16({}{});", this_str, value), - U32 => format!("serializer.serializeU32({}{});", this_str, value), - U64 => format!("serializer.serializeU64({}{});", this_str, value), - U128 => format!("serializer.serializeU128({}{});", this_str, value), - F32 => format!("serializer.serializeF32({}{});", this_str, value), - F64 => format!("serializer.serializeF64({}{});", this_str, value), - Char => format!("serializer.serializeChar({}{});", this_str, value), - Str => format!("serializer.serializeStr({}{});", this_str, value), - Bytes => format!("serializer.serializeBytes({}{});", this_str, value), - _ => format!( - "Helpers.serialize{}({}{}, serializer);", - common::mangle_type(format).to_camel_case(), - this_str, - value - ), - } - } - - fn quote_deserialize(&self, format: &Format) -> String { - use Format::*; - match format { - TypeName(name) => format!( - "{}.deserialize(deserializer)", - self.quote_qualified_name(name) - ), - Unit => "deserializer.deserializeUnit()".to_string(), - Bool => "deserializer.deserializeBool()".to_string(), - I8 => "deserializer.deserializeI8()".to_string(), - I16 => "deserializer.deserializeI16()".to_string(), - I32 => "deserializer.deserializeI32()".to_string(), - I64 => "deserializer.deserializeI64()".to_string(), - I128 => "deserializer.deserializeI128()".to_string(), - U8 => "deserializer.deserializeU8()".to_string(), - U16 => "deserializer.deserializeU16()".to_string(), - U32 => "deserializer.deserializeU32()".to_string(), - U64 => "deserializer.deserializeU64()".to_string(), - U128 => "deserializer.deserializeU128()".to_string(), - F32 => "deserializer.deserializeF32()".to_string(), - F64 => "deserializer.deserializeF64()".to_string(), - Char => "deserializer.deserializeChar()".to_string(), - Str => "deserializer.deserializeStr()".to_string(), - Bytes => "deserializer.deserializeBytes()".to_string(), - _ => format!( - "Helpers.deserialize{}(deserializer)", - common::mangle_type(format).to_camel_case(), - ), - } - } - - fn output_serialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> { - use Format::*; - - write!( - self.out, - "static serialize{}(value: {}, serializer: Serializer): void {{", - name.to_camel_case(), - self.quote_type(format0) - )?; - self.out.indent(); - match format0 { - Option(format) => { - write!( - self.out, - r#" -if (value) {{ - serializer.serializeOptionTag(true); - {} -}} else {{ - serializer.serializeOptionTag(false); -}} -"#, - self.quote_serialize_value("value", format, false) - )?; - } - - Seq(format) => { - write!( - self.out, - r#" -serializer.serializeLen(value.length); -value.forEach((item: {}) => {{ - {} -}}); -"#, - self.quote_type(format), - self.quote_serialize_value("item", format, false) - )?; - } - - Map { key, value } => { - write!( - self.out, - r#" -serializer.serializeLen(value.size); -const offsets: number[] = []; -for (const [k, v] of value.entries()) {{ - offsets.push(serializer.getBufferOffset()); - {} - {} -}} -serializer.sortMapEntries(offsets); -"#, - self.quote_serialize_value("k", key, false), - self.quote_serialize_value("v", value, false) - )?; - } - - Tuple(formats) => { - writeln!(self.out)?; - for (index, format) in formats.iter().enumerate() { - let expr = format!("value[{}]", index); - writeln!( - self.out, - "{}", - self.quote_serialize_value(&expr, format, false) - )?; - } - } - - TupleArray { - content, - size: _size, - } => { - write!( - self.out, - r#" -value.forEach((item) =>{{ - {} -}}); -"#, - self.quote_serialize_value("item[0]", content, false) - )?; - } - - _ => panic!("unexpected case"), - } - self.out.unindent(); - writeln!(self.out, "}}\n") - } - - fn output_deserialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> { - use Format::*; - - write!( - self.out, - "static deserialize{}(deserializer: Deserializer): {} {{", - name.to_camel_case(), - self.quote_type(format0), - )?; - self.out.indent(); - match format0 { - Option(format) => { - write!( - self.out, - r#" -const tag = deserializer.deserializeOptionTag(); -if (!tag) {{ - return null; -}} else {{ - return {}; -}} -"#, - self.quote_deserialize(format), - )?; - } - - Seq(format) => { - write!( - self.out, - r#" -const length = deserializer.deserializeLen(); -const list: {} = []; -for (let i = 0; i < length; i++) {{ - list.push({}); -}} -return list; -"#, - self.quote_type(format0), - self.quote_deserialize(format) - )?; - } - - Map { key, value } => { - write!( - self.out, - r#" -const length = deserializer.deserializeLen(); -const obj = new Map<{0}, {1}>(); -let previousKeyStart = 0; -let previousKeyEnd = 0; -for (let i = 0; i < length; i++) {{ - const keyStart = deserializer.getBufferOffset(); - const key = {2}; - const keyEnd = deserializer.getBufferOffset(); - if (i > 0) {{ - deserializer.checkThatKeySlicesAreIncreasing( - [previousKeyStart, previousKeyEnd], - [keyStart, keyEnd]); - }} - previousKeyStart = keyStart; - previousKeyEnd = keyEnd; - const value = {3}; - obj.set(key, value); -}} -return obj; -"#, - self.quote_type(key), - self.quote_type(value), - self.quote_deserialize(key), - self.quote_deserialize(value), - )?; - } - - Tuple(formats) => { - write!( - self.out, - r#" -return [{} -]; -"#, - formats - .iter() - .map(|f| format!("\n {}", self.quote_deserialize(f))) - .collect::>() - .join(",") - )?; - } - - TupleArray { content, size } => { - write!( - self.out, - r#" -const list: {} = []; -for (let i = 0; i < {}; i++) {{ - list.push([{}]); -}} -return list; -"#, - self.quote_type(format0), - size, - self.quote_deserialize(content) - )?; - } - - _ => panic!("unexpected case"), - } - self.out.unindent(); - writeln!(self.out, "}}\n") - } - - fn output_variant( - &mut self, - base: &str, - index: u32, - name: &str, - variant: &VariantFormat, - ) -> Result<()> { - use VariantFormat::*; - let fields = match variant { - Unit => Vec::new(), - NewType(format) => vec![Named { - name: "value".to_string(), - value: format.as_ref().clone(), - }], - Tuple(formats) => formats - .iter() - .enumerate() - .map(|(i, f)| Named { - name: format!("field{}", i), - value: f.clone(), - }) - .collect(), - Struct(fields) => fields.clone(), - Variable(_) => panic!("incorrect value"), - }; - self.output_struct_or_variant_container(Some(base), Some(index), name, &fields) - } - - fn output_variants( - &mut self, - base: &str, - variants: &BTreeMap>, - ) -> Result<()> { - for (index, variant) in variants { - self.output_variant(base, *index, &variant.name, &variant.value)?; - } - Ok(()) - } - - fn output_struct_or_variant_container( - &mut self, - variant_base: Option<&str>, - variant_index: Option, - name: &str, - fields: &[Named], - ) -> Result<()> { - let mut variant_base_name = String::new(); - - // Beginning of class - if let Some(base) = variant_base { - writeln!(self.out)?; - self.output_comment(name)?; - writeln!( - self.out, - "export class {0}Variant{1} extends {0} {{", - base, name - )?; - variant_base_name = format!("{0}Variant", base); - } else { - self.output_comment(name)?; - writeln!(self.out, "export class {} {{", name)?; - } - if !fields.is_empty() { - writeln!(self.out)?; - } - // Constructor. - writeln!( - self.out, - "constructor ({}) {{", - fields - .iter() - .map(|f| { format!("public {}: {}", &f.name, self.quote_type(&f.value)) }) - .collect::>() - .join(", ") - )?; - if let Some(_base) = variant_base { - self.out.indent(); - writeln!(self.out, "super();")?; - self.out.unindent(); - } - writeln!(self.out, "}}\n")?; - // Serialize - if self.generator.config.serialization { - writeln!( - self.out, - "public serialize(serializer: Serializer): void {{", - )?; - self.out.indent(); - if let Some(index) = variant_index { - writeln!(self.out, "serializer.serializeVariantIndex({});", index)?; - } - for field in fields { - writeln!( - self.out, - "{}", - self.quote_serialize_value(&field.name, &field.value, true) - )?; - } - self.out.unindent(); - writeln!(self.out, "}}\n")?; - } - // Deserialize (struct) or Load (variant) - if self.generator.config.serialization { - if variant_index.is_none() { - writeln!( - self.out, - "static deserialize(deserializer: Deserializer): {} {{", - name, - )?; - } else { - writeln!( - self.out, - "static load(deserializer: Deserializer): {}{} {{", - variant_base_name, name, - )?; - } - self.out.indent(); - for field in fields { - writeln!( - self.out, - "const {} = {};", - field.name, - self.quote_deserialize(&field.value) - )?; - } - writeln!( - self.out, - r#"return new {0}{1}({2});"#, - variant_base_name, - name, - fields - .iter() - .map(|f| f.name.to_string()) - .collect::>() - .join(",") - )?; - self.out.unindent(); - writeln!(self.out, "}}\n")?; - } - writeln!(self.out, "}}") - } - - fn output_enum_container( - &mut self, - name: &str, - variants: &BTreeMap>, - ) -> Result<()> { - self.output_comment(name)?; - writeln!(self.out, "export abstract class {} {{", name)?; - if self.generator.config.serialization { - writeln!( - self.out, - "abstract serialize(serializer: Serializer): void;\n" - )?; - write!( - self.out, - "static deserialize(deserializer: Deserializer): {} {{", - name - )?; - self.out.indent(); - writeln!( - self.out, - r#" -const index = deserializer.deserializeVariantIndex(); -switch (index) {{"#, - )?; - self.out.indent(); - for (index, variant) in variants { - writeln!( - self.out, - "case {}: return {}Variant{}.load(deserializer);", - index, name, variant.name, - )?; - } - writeln!( - self.out, - "default: throw new Error(\"Unknown variant index for {}: \" + index);", - name, - )?; - self.out.unindent(); - writeln!(self.out, "}}")?; - self.out.unindent(); - writeln!(self.out, "}}")?; - } - writeln!(self.out, "}}\n")?; - self.output_variants(name, variants)?; - Ok(()) - } - - fn output_container(&mut self, name: &str, format: &ContainerFormat) -> Result<()> { - use ContainerFormat::*; - let fields = match format { - UnitStruct => Vec::new(), - NewTypeStruct(format) => vec![Named { - name: "value".to_string(), - value: format.as_ref().clone(), - }], - TupleStruct(formats) => formats - .iter() - .enumerate() - .map(|(i, f)| Named { - name: format!("field{}", i), - value: f.clone(), - }) - .collect::>(), - Struct(fields) => fields.clone(), - Enum(variants) => { - self.output_enum_container(name, variants)?; - return Ok(()); - } - }; - self.output_struct_or_variant_container(None, None, name, &fields) - } -} - -/// Installer for generated source files in TypeScript. -pub struct Installer { - install_dir: PathBuf, -} - -impl Installer { - pub fn new(install_dir: PathBuf) -> Self { - Installer { install_dir } - } - - fn install_runtime( - &self, - source_dir: include_dir::Dir, - path: &str, - ) -> std::result::Result<(), Box> { - let dir_path = self.install_dir.join(path); - std::fs::create_dir_all(&dir_path)?; - for entry in source_dir.files() { - let mut file = std::fs::File::create(dir_path.join(entry.path()))?; - file.write_all(entry.contents())?; - } - Ok(()) - } -} - -impl crate::SourceInstaller for Installer { - type Error = Box; - - fn install_module( - &self, - config: &CodeGeneratorConfig, - registry: &Registry, - ) -> std::result::Result<(), Self::Error> { - let dir_path = self.install_dir.join(&config.module_name); - std::fs::create_dir_all(&dir_path)?; - let source_path = dir_path.join("mod.ts"); - let mut file = std::fs::File::create(source_path)?; - - let generator = CodeGenerator::new(config); - generator.output(&mut file, registry)?; - Ok(()) - } - - fn install_serde_runtime(&self) -> std::result::Result<(), Self::Error> { - self.install_runtime(include_directory!("runtime/typescript/serde"), "serde") - } - - fn install_bincode_runtime(&self) -> std::result::Result<(), Self::Error> { - self.install_runtime(include_directory!("runtime/typescript/bincode"), "bincode") - } - - fn install_bcs_runtime(&self) -> std::result::Result<(), Self::Error> { - self.install_runtime(include_directory!("runtime/typescript/bcs"), "bcs") - } -} +impl<'a, T: Write> TypeScriptEmitter<'a, T> { + fn output_preamble(&mut self) -> Result<()> { + writeln!(self.out, r#"import type * as $t from "./serde.ts""#)?; + writeln!(self.out, r#"import {{ BincodeReader, BincodeWriter }} from "./bincode.ts""#)?; + for namespace in self.generator.namespaces_to_import.iter() { + writeln!(self.out, "import * as {} from '../{}/mod.ts';\n", namespace.to_camel_case(), namespace)?; + } + Ok(()) + } + + fn generate_container(&mut self, name: &str, container: &ContainerFormat) -> Result<()> { + // Encode + writeln!(self.out, "export const {name} = {{")?; + self.out.indent(); + + writeln!(self.out, "encode(value: {name}, writer = new BincodeWriter()) {{")?; + self.out.indent(); + + match container { + ContainerFormat::UnitStruct => { + writeln!(self.out, "{}", self.quote_write_value("null", &Format::Unit))?; + } + ContainerFormat::Struct(fields) => { + for field in fields.iter() { + writeln!(self.out, "{}", self.quote_write_value(&format!("value.{}", field.name), &field.value))?; + } + } + ContainerFormat::NewTypeStruct(inner_type) => { + writeln!(self.out, "{}", self.quote_write_value(&format!("value"), inner_type))?; + } + ContainerFormat::TupleStruct(inner_types) => { + for (i, inner) in inner_types.iter().enumerate() { + writeln!(self.out, "{}", self.quote_write_value(&format!("value.${i}"), inner))?; + } + } + ContainerFormat::Enum(variants) => { + self.generate_enum_container(name, variants)?; + return Ok(()); + } + } + + writeln!(self.out, "return writer.get_bytes()")?; + + self.out.unindent(); + writeln!(self.out, "}},")?; + + + // Decode + writeln!(self.out, "decode(input: Uint8Array, reader = new BincodeReader(input)) {{")?; + self.out.indent(); + + match container { + ContainerFormat::UnitStruct => { + writeln!(self.out, "let value: $t.unit = {}", self.quote_read_value(&Format::Unit))?; + } + ContainerFormat::NewTypeStruct(inner) => { + writeln!(self.out, "let value: {name} = {}", self.quote_read_value(inner))?; + } + ContainerFormat::TupleStruct(inner_types) => { + writeln!(self.out, "let value: {name} = {{")?; + self.out.indent(); + for (i, inner) in inner_types.iter().enumerate() { + writeln!(self.out, "${i}: {},", self.quote_read_value(&inner))?; + } + self.out.unindent(); + writeln!(self.out, "}}")?; + } + ContainerFormat::Struct(fields) => { + writeln!(self.out, "let value: {name} = {{")?; + self.out.indent(); + for field in fields.iter() { + writeln!(self.out, "{}: {},", field.name, self.quote_read_value(&field.value))?; + } + self.out.unindent(); + writeln!(self.out, "}}")?; + }, + _ => unreachable!(), + } + + writeln!(self.out, "return value")?; + + self.out.unindent(); + writeln!(self.out, "}}")?; // decode end + + self.out.unindent(); + writeln!(self.out, "}}")?; // object end + + Ok(()) + } + + fn generate_enum_container(&mut self, name: &str, variants: &BTreeMap>) -> Result<()> { + writeln!(self.out, "switch (value.$) {{")?; + self.out.indent(); + + for (index, variant) in variants { + writeln!(self.out, r#"case "{}": {{"#, variant.name.to_snake_case())?; + self.out.indent(); + writeln!(self.out, "writer.write_variant_index({index})"); + + match &variant.value { + VariantFormat::Unit => { + writeln!(self.out, "{}", self.quote_write_value("value.$0", &Format::Unit)); + }, + VariantFormat::NewType(inner) => { + writeln!(self.out, "{}", self.quote_write_value("value.$0", inner)); + } + VariantFormat::Tuple(members) => { + let tuple = Format::Tuple(members.clone()); + writeln!(self.out, "{}", self.quote_write_value("value", &tuple)); + } + VariantFormat::Struct(fields) => { + for field in fields { + writeln!(self.out, "{}", self.quote_write_value(&format!("value.{}", field.name), &field.value))?; + } + } + VariantFormat::Variable(_) => panic!("not supported") + } + writeln!(self.out, "break")?; + self.out.unindent(); + writeln!(self.out, "}}")?; // case end + } + + self.out.unindent(); + writeln!(self.out, "}}")?; // switch end + + writeln!(self.out, "return writer.get_bytes()"); + self.out.unindent(); + writeln!(self.out, "}},")?; // encode end + + writeln!(self.out, "decode(input: Uint8Array, reader = new BincodeReader(input)) {{")?; + self.out.indent(); + + writeln!(self.out, r#"let value: {name}"#); + + writeln!(self.out, "switch (reader.read_variant_index()) {{")?; + self.out.indent(); + + for (index, variant) in variants { + writeln!(self.out, r#"case {index}: {{"#)?; + self.out.indent(); + + writeln!(self.out, r#"value = {{"#); + self.out.indent(); + writeln!(self.out, r#"$: "{0}","#, variant.name.to_snake_case()); + + match &variant.value { + VariantFormat::Unit => { + writeln!(self.out, "$0: {}", self.quote_read_value(&Format::Unit)); + }, + VariantFormat::Tuple(members) => { + let tuple = Format::Tuple(members.clone()); + for (i, member) in members.iter().enumerate() { + writeln!(self.out, "${i}: {},", self.quote_read_value(&member)); + } + } + VariantFormat::NewType(inner) => { + writeln!(self.out, "$0: {},", self.quote_read_value(inner)); + } + VariantFormat::Struct(fields) => { + for field in fields { + writeln!(self.out, "{}: {},", field.name, self.quote_read_value(&field.value))?; + } + } + VariantFormat::Variable(_) => panic!("not supported") + } + + self.out.unindent(); + writeln!(self.out, r#"}} satisfies Extract<{0}, {{ $: "{1}" }}>"#, name, variant.name.to_snake_case())?; + + writeln!(self.out, "break")?; + self.out.unindent(); + writeln!(self.out, "}}")?; // case end + } + + self.out.unindent(); + writeln!(self.out, "}}")?; // switch end + + writeln!(self.out)?; + writeln!(self.out, "return value")?; + + self.out.unindent(); + writeln!(self.out, "}}")?; // decode end + + self.out.unindent(); + writeln!(self.out, "}}")?; // object end + + Ok(()) + } + + fn generate_container_typedef(&mut self, name: &str, container: &ContainerFormat) -> Result<()> { + match container { + ContainerFormat::UnitStruct => { + writeln!(self.out, "export type {name} = $t.unit")?; + } + ContainerFormat::TupleStruct(fields) => { + writeln!(self.out, "export type {name} = $t.Tuple<[{}]>", self.quote_types(&fields, ", "))?; + self.out.unindent(); + } + ContainerFormat::Struct(fields) => { + writeln!(self.out, "export type {name} = {{")?; + self.out.indent(); + for field in fields { + match field.value { + Format::Unit | Format::Option {..} => { + writeln!(self.out, "{}?: {},", field.name, self.quote_type(&field.value))?; + } + _ => { writeln!(self.out, "{}: {},", field.name, self.quote_type(&field.value))?; } + } + } + self.out.unindent(); + writeln!(self.out, "}}")?; + } + ContainerFormat::NewTypeStruct(format) => { + writeln!(self.out, "export type {name} = {}", self.quote_type(format))?; + } + ContainerFormat::Enum(variants) => { + // TODO https://github.com/zefchain/serde-reflection/issues/45 + writeln!(self.out, "export type {name} = ")?; + self.out.indent(); + for (_index, variant) in variants { + let variant_name_snake = variant.name.to_snake_case(); + match &variant.value { + VariantFormat::Unit => { + writeln!(self.out, r#"| {{ $: "{0}", $0?: {1} }}"#, variant_name_snake, self.quote_type(&Format::Unit))?; + } + VariantFormat::Struct(fields) => { + let fields_str = fields.iter().map(|f| format!("{}: {}", f.name, self.quote_type(&f.value))).collect::>().join(", "); + writeln!(self.out, r#"| {{ $: "{0}", {1} }}"#, variant_name_snake, fields_str)?; + } + VariantFormat::NewType(t) => { + writeln!(self.out, r#"| {{ $: "{0}", $0: {1} }}"#, variant_name_snake, self.quote_type(&t))?; + } + VariantFormat::Tuple(t) => { + writeln!(self.out, r#"| {{ $: "{0}" }} & {1}"#, variant_name_snake, self.quote_type(&Format::Tuple(t.clone())))?; + } + VariantFormat::Variable(v) => panic!("unknown variant format") + } + } + self.out.unindent(); + } + _ => panic!("format not implemented") + } + + Ok(()) + } + + fn quote_qualified_name(&self, name: &str) -> String { + self.generator.external_qualified_names.get(name).cloned().unwrap_or_else(|| name.to_string()) + } + + fn output_comment(&mut self, name: &str) -> std::io::Result<()> { + let path = vec![name.to_string()]; + if let Some(doc) = self.generator.config.comments.get(&path) { + let text = textwrap::indent(doc, " * ").replace("\n\n", "\n *\n"); + writeln!(self.out, "/**\n{} */", text)?; + } + Ok(()) + } + + fn quote_type(&self, format: &Format) -> String { + use Format::*; + let str = match format { + Unit => "$t.unit", + Bool => "$t.bool", + I8 => "$t.i8", + I16 => "$t.i16", + I32 => "$t.i32", + I64 => "$t.i64", + I128 => "$t.i128", + U8 => "$t.u8", + U16 => "$t.u16", + U32 => "$t.u32", + U64 => "$t.u64", + U128 => "$t.u128", + F32 => "$t.f32", + F64 => "$t.f64", + Char => "$t.char", + Str => "$t.str", + Bytes => "$t.bytes", + + Option(format) => &format!("$t.Optional<{}>", self.quote_type(format)), + Seq(format) => &format!("$t.Seq<{}>", self.quote_type(format)), + Map { key, value } => &format!("$t.Map<{}, {}>", self.quote_type(key), self.quote_type(value)), + Tuple(formats) => &format!("$t.Tuple<[{}]>", self.quote_types(formats, ", ")), + TupleArray { content, .. } => &format!("$t.ListTuple<[{}]>", self.quote_type(content)), + + TypeName(x) => &self.quote_qualified_name(x), + + Variable(_) => panic!("unexpected value"), + }; + str.to_string() + } + + fn quote_types(&self, formats: &[Format], sep: &str) -> String { + formats.iter().map(|f| self.quote_type(f)).collect::>().join(sep) + } + + fn quote_write_value(&self, value: &str, format: &Format) -> String { + use Format::*; + match format { + TypeName(typename) => format!("{typename}.encode({value}, writer)"), + Unit => format!("writer.write_unit({value})"), + Bool => format!("writer.write_bool({value})"), + I8 => format!("writer.write_i8({value})"), + I16 => format!("writer.write_i16({value})"), + I32 => format!("writer.write_i32({value})"), + I64 => format!("writer.write_i64({value})"), + I128 => format!("writer.write_i128({value})"), + U8 => format!("writer.write_u8({value})"), + U16 => format!("writer.write_u16({value})"), + U32 => format!("writer.write_u32({value})"), + U64 => format!("writer.write_u64({value})"), + U128 => format!("writer.write_u128({value})"), + F32 => format!("writer.write_f32({value})"), + F64 => format!("writer.write_f64({value})"), + Char => format!("writer.write_char({value})"), + Str => format!("writer.write_string({value})"), + Bytes => format!("writer.write_bytes({value})"), Option(inner) => { + formatdoc! { + " + if ({value}) {{ + writer.write_option_tag(true) + {} + }} + else writer.write_option_tag(false) + ", + self.quote_write_value(value, inner) + } + }, + Seq(format) => { + formatdoc!(" + writer.write_length({value}.length) + for (let item of {value}) {{ + {} + }}", + self.quote_write_value("item", format) + ) + } + Map { key: map_key, value: map_value } => { + format! { + "writer.write_map({value}, {}, {})", + self.quote_write_value("", map_key).replace("()", ".bind(writer)"), + self.quote_write_value("", map_value).replace("()", ".bind(writer)") + } + } + Tuple(formats) => { + use std::fmt::Write; + let mut lines = Vec::new(); + for (index, format) in formats.iter().enumerate() { + let expr = format!("{value}.${}", index); + lines.push(self.quote_write_value(&expr, format)); + } + lines.join("\n") + } + TupleArray { content, .. } => { + formatdoc!(" + for (let item of {value}) {{ + {} + }}", + self.quote_write_value("item[0]", content) + ) + } + _ => panic!("unexpected case"), + } + } + + fn quote_read_value(&self, format: &Format) -> String { + use Format::*; + let str = match format { + TypeName(name) => &format!("{}.decode(input, reader)", self.quote_qualified_name(name)), + Unit => "reader.read_unit()", + Bool => "reader.read_bool()", + I8 => "reader.read_i8()", + I16 => "reader.read_i16()", + I32 => "reader.read_i32()", + I64 => "reader.read_i64()", + I128 => "reader.read_i128()", + U8 => "reader.read_u8()", + U16 => "reader.read_u16()", + U32 => "reader.read_u32()", + U64 => "reader.read_u64()", + U128 => "reader.read_u128()", + F32 => "reader.read_f32()", + F64 => "reader.read_f64()", + Char => "reader.read_char()", + Str => "reader.read_string()", + Bytes => "reader.read_bytes()", + Option(format) => { + &format!("reader.read_option_tag() ? {} : null", self.quote_read_value(format)) + } + Seq(format) => { + &format!( + "reader.read_list<{}>(() => {})", + self.quote_type(format), + self.quote_read_value(format) + ) + } + Map { key, value } => { + &format!( + "reader.read_map<{}, {}>({}, {})", + self.quote_type(key), + self.quote_type(value), + self.quote_read_value(key).replace("()", ".bind(reader)"), + self.quote_read_value(value).replace("()", ".bind(reader)"), + ) + } + Tuple(formats) => { + let mut buf = Vec::new(); + let mut writer = IndentedWriter::new(&mut buf, IndentConfig::Tab); + writeln!(writer, "{{"); + writer.indent(); + for (i, f) in formats.iter().enumerate() { + writeln!(writer, "${i}: {},", self.quote_read_value(f)); + } + writer.unindent(); + write!(writer, "}}"); + Box::leak(String::from_utf8(buf).unwrap().into_boxed_str()) + } + TupleArray { content, size } => { + &format!( + "reader.read_list<{}>(() => {}, {})", + self.quote_type(format), self.quote_read_value(content), size, + ) + } + Variable(_) => panic!("unsupported value") + }; + str.to_string() + } + +} \ No newline at end of file diff --git a/suite/typescript/.gitignore b/suite/typescript/.gitignore new file mode 100644 index 000000000..7600b1b2c --- /dev/null +++ b/suite/typescript/.gitignore @@ -0,0 +1,4 @@ +node_modules +target +.devenv +.envrc \ No newline at end of file diff --git a/suite/typescript/Cargo.lock b/suite/typescript/Cargo.lock new file mode 100644 index 000000000..a759fa7d1 --- /dev/null +++ b/suite/typescript/Cargo.lock @@ -0,0 +1,366 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "include_dir" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b56e147e6187d61e9d0f039f10e070d0c0a887e24fe0bb9ca3f29bfde62cab" +dependencies = [ + "glob", + "include_dir_impl", + "proc-macro-hack", +] + +[[package]] +name = "include_dir_impl" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0c890c85da4bab7bce4204c707396bbd3c6c8a681716a51c8814cfc2b682df" +dependencies = [ + "anyhow", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-generate" +version = "0.26.0" +dependencies = [ + "heck", + "include_dir", + "indoc", + "phf", + "serde", + "serde-reflection", + "textwrap", +] + +[[package]] +name = "serde-reflection" +version = "0.4.0" +dependencies = [ + "once_cell", + "serde", + "thiserror", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serdegen-bincode" +version = "0.1.0" +dependencies = [ + "bincode", + "serde", + "serde-generate", + "serde-reflection", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" +dependencies = [ + "smawk", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/suite/typescript/Cargo.toml b/suite/typescript/Cargo.toml new file mode 100644 index 000000000..0d6367eee --- /dev/null +++ b/suite/typescript/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ts-generator" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "generator" +path = "./rs/generator.rs" + +[dependencies] +bincode = { version = "2", features = ["serde"] } +serde = { version = "1" } +serde-generate = { path = "../../serde-generate" } +serde-reflection = { path = "../../serde-reflection" } +serde_json = { version = "1" } diff --git a/suite/typescript/flake.lock b/suite/typescript/flake.lock new file mode 100644 index 000000000..f027e9e34 --- /dev/null +++ b/suite/typescript/flake.lock @@ -0,0 +1,620 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": "devenv_2", + "flake-compat": [ + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1712055811, + "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", + "owner": "cachix", + "repo": "cachix", + "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat_3", + "nix": "nix_2", + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks_3" + }, + "locked": { + "lastModified": 1727098005, + "narHash": "sha256-aKRUBfaDCJxdM8f/9RNmGJa82oZXDbOl6hxc/s2nLss=", + "owner": "cachix", + "repo": "devenv", + "rev": "f318d27a4637aff765a378106d82dfded124c3b3", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "rev": "f318d27a4637aff765a378106d82dfded124c3b3", + "type": "github" + } + }, + "devenv_2": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "flake-compat" + ], + "nix": "nix", + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix", + "pre-commit-hooks": [ + "devenv", + "cachix", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1708704632, + "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", + "owner": "cachix", + "repo": "devenv", + "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "python-rewrite", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_3": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741352980, + "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1725234343, + "narHash": "sha256-+ebgonl3NbiKD2UD0x4BszCZQ6sTfL4xioaM49o5B3Y=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "567b938d64d4b4112ee253b9274472dc3a346eb6", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "cachix", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_2": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "libgit2": { + "flake": false, + "locked": { + "lastModified": 1740952128, + "narHash": "sha256-lmG/Vg3hxb/dRVPFhkQRB1dp8XT2YJzCvkPjm7yXP/A=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "21a351b0ed207d0871cb23e09c027d1ee42eae98", + "type": "github" + }, + "original": { + "owner": "libgit2", + "repo": "libgit2", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix_2": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": "nixpkgs-23-11", + "nixpkgs-regression": "nixpkgs-regression_2", + "pre-commit-hooks": "pre-commit-hooks_2" + }, + "locked": { + "lastModified": 1734114420, + "narHash": "sha256-n52PUzub5jZWc8nI/sR7UICOheU8rNA+YZ73YaHeCBg=", + "owner": "domenkozar", + "repo": "nix", + "rev": "bde6a1a0d1f2af86caa4d20d23eca019f3d57eee", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.24", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717159533, + "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1725233747, + "narHash": "sha256-Ss8QWLXdr2JCBPcYChJhz4xJm+h/xjl4G0c0XlP6a74=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/356624c12086a18f2ea2825fed34523d60ccc4e3.tar.gz" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-regression_2": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1710695816, + "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "614b4613980a522ba49f0d194531beddbb7220d3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1735651292, + "narHash": "sha256-YLbzcBtYo1/FEzFsB3AnM16qFc6fWPMIoOuSoDwvg9g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0da3c44a9460a26d2025ec3ed2ec60a895eb1114", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1740019556, + "narHash": "sha256-vn285HxnnlHLWnv59Og7muqECNMS33mWLM14soFIv2g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "dad564433178067be1fbdfcce23b546254b6d641", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "dad564433178067be1fbdfcce23b546254b6d641", + "type": "github" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1692876271, + "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "cachix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741379162, + "narHash": "sha256-srpAbmJapkaqGRE3ytf3bj4XshspVR5964OX5LfjDWc=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "b5a62751225b2f62ff3147d0a334055ebadcd5cc", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_2": { + "inputs": { + "flake-compat": [ + "devenv", + "nix" + ], + "gitignore": [ + "devenv", + "nix" + ], + "nixpkgs": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741379162, + "narHash": "sha256-srpAbmJapkaqGRE3ytf3bj4XshspVR5964OX5LfjDWc=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "b5a62751225b2f62ff3147d0a334055ebadcd5cc", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_3": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils_2", + "gitignore": "gitignore_2", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1713775815, + "narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_3" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/suite/typescript/flake.nix b/suite/typescript/flake.nix new file mode 100644 index 000000000..bbf9be67f --- /dev/null +++ b/suite/typescript/flake.nix @@ -0,0 +1,88 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?rev=dad564433178067be1fbdfcce23b546254b6d641"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + + devenv.url = "github:cachix/devenv?rev=f318d27a4637aff765a378106d82dfded124c3b3"; # https://github.com/cachix/devenv/issues/1513 + devenv.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = {self, ...} @ inputs: + with builtins; let + lib = inputs.nixpkgs.lib; + in + with lib; + inputs.flake-parts.lib.mkFlake { inherit inputs; specialArgs = {inherit lib;}; } + ({moduleWithSystem, ...}: { + imports = with inputs; [devenv.flakeModule]; + systems = ["x86_64-linux"]; + perSystem = { config, system, self', inputs', ... }: let + pkgs = import inputs.nixpkgs { inherit system; config.allowUnfree = true; }; + in { + _module.args = {inherit pkgs;}; + devenv.shells.default = {config, ...} @ devenvArgs: let + inherit (config.devenv) root state profile; + in { + enterShell = let + # ANSI colors: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + commands = pipe devenvArgs.config.scripts [ + attrNames + (groupBy (cmd: elemAt (splitString ":" cmd) 0)) + (mapAttrsToList (group: commands: let + splitted = pipe commands [ + (sortOn stringLength) + (map (removePrefix group)) + (concatStringsSep "|") + ]; + in "$(tput setaf 226)${group}$(tput sgr0)|${splitted}")) + (intersperse "\n") + concatStrings + ]; + in '' + echo "$(tput setaf 118)Welcome to serde-reflection typescript suite.$(tput sgr0)" + echo "${commands}" | ${pkgs.unixtools.column}/bin/column --table -W 1 -T 1 -t -s "|" + ''; + packages = with pkgs; [nodejs_23 nodePackages.pnpm protobuf_28]; + scripts."gen:proto".exec = '' + pushd ${root} + rm -rf ts/proto/* + mkdir -p ts/proto + ${concatStringsSep " \\\n" [ + "protoc" + "--plugin ${root}/node_modules/.bin/protoc-gen-ts_proto" + "--ts_proto_out ts/proto" + "--ts_proto_opt esModuleInterop=true,snakeToCamel=false,forceLong=number,oneof=unions,outputJsonMethods=false,env=both,importSuffix=.js" + + "--proto_path schema-proto" + "--proto_path ${pkgs.protobuf}/include/google/protobuf/" + ''$(find schema-proto -iname "*.proto")'' + ]} + popd + ''; + scripts."gen:bincode".exec = '' + pushd ${root} + rm -f ts/bincode/registry.ts + cargo run + popd + ''; + scripts."run:test".exec = '' + pushd ${root} + node --experimental-strip-types --no-warnings ts/test.ts + popd + ''; + scripts."run:benchmarks".exec = '' + pushd ${root} + node --experimental-strip-types --no-warnings ts/bench.ts + popd + ''; + scripts."run:suite".exec = '' + gen:bincode + gen:proto + run:test + run:benchmarks + ''; + }; + }; + }); +} diff --git a/suite/typescript/package.json b/suite/typescript/package.json new file mode 100644 index 000000000..7daeb082e --- /dev/null +++ b/suite/typescript/package.json @@ -0,0 +1,14 @@ +{ + "name": "js-bin-benchmark", + "type": "module", + "version": "0.0.0", + "dependencies": { + "long": "^5.3.1", + "protobufjs": "^7.4.0", + "tinybench": "^2.9.0", + "ts-proto": "1" + }, + "devDependencies": { + "@types/node": "^22.13.10" + } +} diff --git a/suite/typescript/pnpm-lock.yaml b/suite/typescript/pnpm-lock.yaml new file mode 100644 index 000000000..a4c75daaa --- /dev/null +++ b/suite/typescript/pnpm-lock.yaml @@ -0,0 +1,170 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + long: + specifier: ^5.3.1 + version: 5.3.1 + protobufjs: + specifier: ^7.4.0 + version: 7.4.0 + tinybench: + specifier: ^2.9.0 + version: 2.9.0 + ts-proto: + specifier: '1' + version: 1.181.2 + devDependencies: + '@types/node': + specifier: ^22.13.10 + version: 22.13.10 + +packages: + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + + case-anything@2.1.13: + resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} + engines: {node: '>=12.13'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + dprint-node@1.0.8: + resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} + + long@5.3.1: + resolution: {integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==} + + protobufjs@7.4.0: + resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} + engines: {node: '>=12.0.0'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + ts-poet@6.9.0: + resolution: {integrity: sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ==} + + ts-proto-descriptors@1.16.0: + resolution: {integrity: sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA==} + + ts-proto@1.181.2: + resolution: {integrity: sha512-knJ8dtjn2Pd0c5ZGZG8z9DMiD4PUY8iGI9T9tb8DvGdWRMkLpf0WcPO7G+7cmbZyxvNTAG6ci3fybEaFgMZIvg==} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + +snapshots: + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 + + case-anything@2.1.13: {} + + detect-libc@1.0.3: {} + + dprint-node@1.0.8: + dependencies: + detect-libc: 1.0.3 + + long@5.3.1: {} + + protobufjs@7.4.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.13.10 + long: 5.3.1 + + tinybench@2.9.0: {} + + ts-poet@6.9.0: + dependencies: + dprint-node: 1.0.8 + + ts-proto-descriptors@1.16.0: + dependencies: + long: 5.3.1 + protobufjs: 7.4.0 + + ts-proto@1.181.2: + dependencies: + case-anything: 2.1.13 + protobufjs: 7.4.0 + ts-poet: 6.9.0 + ts-proto-descriptors: 1.16.0 + + undici-types@6.20.0: {} diff --git a/suite/typescript/readme.md b/suite/typescript/readme.md new file mode 100644 index 000000000..7f8c7ecbe --- /dev/null +++ b/suite/typescript/readme.md @@ -0,0 +1,30 @@ +## JavaScript benchamrks. + +Run: +```sh +nix develop . --impure +pnpm i +gen:proto && gen:bincode && run:test && run:benchmarks +``` + +## Results +### Encode +``` +┌─────────┬───────────────────────────────┬─────────────┬────────────────────┬──────────┬─────────┐ +│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │ +├─────────┼───────────────────────────────┼─────────────┼────────────────────┼──────────┼─────────┤ +│ 0 │ 'serdegen-bincode:encode' │ '717,309' │ 1394.0992784144942 │ '±0.31%' │ 1075964 │ +│ 1 │ 'JSON:encode' │ '384,683' │ 2599.5422867295706 │ '±0.05%' │ 577025 │ +│ 2 │ 'protobuf-js-ts-proto:encode' │ '1,049,439' │ 952.8891828025206 │ '±0.50%' │ 1574160 │ +└─────────┴───────────────────────────────┴─────────────┴────────────────────┴──────────┴─────────┘ +``` +### Decode +``` +┌─────────┬───────────────────────────────┬───────────┬────────────────────┬──────────┬─────────┐ +│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │ +├─────────┼───────────────────────────────┼───────────┼────────────────────┼──────────┼─────────┤ +│ 0 │ 'serdegen-bincode:decode' │ '894,259' │ 1118.2439247680838 │ '±0.24%' │ 1341389 │ +│ 1 │ 'JSON:decode' │ '460,448' │ 2171.7932150326965 │ '±0.14%' │ 690674 │ +│ 2 │ 'protobuf-js-ts-proto:decode' │ '837,149' │ 1194.5301794026077 │ '±0.18%' │ 1255724 │ +└─────────┴───────────────────────────────┴───────────┴────────────────────┴──────────┴─────────┘ +``` \ No newline at end of file diff --git a/suite/typescript/rs/generator.rs b/suite/typescript/rs/generator.rs new file mode 100644 index 000000000..4fc0fc9e3 --- /dev/null +++ b/suite/typescript/rs/generator.rs @@ -0,0 +1,79 @@ +use std::{collections::HashMap, error::Error}; +use serde::{Deserialize, Serialize}; +use bincode::{Encode, Decode}; + +fn main() -> Result<(), Box> { + use serde_reflection::{Registry, Tracer, TracerConfig}; + let mut tracer = Tracer::new(TracerConfig::default()); + + #[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)] + struct SimpleStruct { + a: u32, + b: String, + } + + #[derive(Debug, Serialize, Deserialize, Encode, Decode)] + enum MultiEnum { + VariantA(i32), + VariantB(String), + VariantC { x: u8, y: f64 }, + UnitVariant, + } + + #[derive(Debug, Serialize, Deserialize, Encode, Decode)] + struct UnitStruct; + + #[derive(Debug, Serialize, Deserialize, Encode, Decode)] + struct NewtypeStruct(i32); + + #[derive(Debug, Serialize, Deserialize, Encode, Decode)] + struct TupleStruct(i32, f64, String); + + #[derive(Debug, Serialize, Deserialize, Encode, Decode)] + struct ComplexStruct { + inner: SimpleStruct, + flag: bool, + items: Vec, + unit: UnitStruct, + newtype: NewtypeStruct, + tuple: TupleStruct, + tupple_inline: (String, i32), + map: HashMap + } + + tracer.trace_simple_type::()?; + tracer.trace_simple_type::()?; + tracer.trace_simple_type::()?; + tracer.trace_simple_type::()?; + tracer.trace_simple_type::()?; + tracer.trace_simple_type::()?; + + let simple_instance = SimpleStruct { a: 42, b: "Hello".into() }; + let enum_instance = MultiEnum::VariantC { x: 5, y: 3.14 }; + let unit_variant = MultiEnum::UnitVariant; + let complex_instance = ComplexStruct { + inner: simple_instance.clone(), + flag: true, + items: vec![MultiEnum::VariantA(10), MultiEnum::VariantB("World".into())], + unit: UnitStruct, + newtype: NewtypeStruct(99), + tuple: TupleStruct(123, 45.67, "Test".into()), + tupple_inline: ("SomeString".into(), 777), + map: HashMap::from_iter([(3, 7)]) + }; + + println!("simple_instance: {:?}", bincode::encode_to_vec(&simple_instance, bincode::config::standard())?); + println!("enum_instance: {:?}", bincode::encode_to_vec(&enum_instance, bincode::config::standard())?); + println!("unit_variant: {:?}", bincode::encode_to_vec(&unit_variant, bincode::config::standard())?); + println!("complex_instance: {:?}", bincode::encode_to_vec(&complex_instance, bincode::config::standard())?); + + let registry = tracer.registry()?; + + use serde_generate::{typescript, CodeGeneratorConfig, Encoding}; + let mut source = Vec::new(); + let config = CodeGeneratorConfig::new("bincode".into()).with_encodings(vec![Encoding::Bincode]); + typescript::CodeGenerator::new(&config).output(&mut source, ®istry)?; + std::fs::write(format!("{}/ts/bincode/registry.ts", env!("CARGO_MANIFEST_DIR")), source)?; + + Ok(()) +} diff --git a/suite/typescript/schema-proto/main.proto b/suite/typescript/schema-proto/main.proto new file mode 100644 index 000000000..23a4c2794 --- /dev/null +++ b/suite/typescript/schema-proto/main.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +message UnitStruct {} + +message NewtypeStruct { + int32 value = 1; +} + +message TupleStruct { + int32 first = 1; + double second = 2; + string third = 3; +} + +message SimpleStruct { + int32 a = 1; + string b = 2; +} + +message ComplexStruct { + SimpleStruct inner = 1; + bool flag = 2; + repeated MultiEnum items = 3; + UnitStruct unit = 4; + int32 newtype = 5; + TupleStruct tuple = 6; + map map = 7; +} + +message MultiEnum { + oneof variant { + VariantA variant_a = 1; + VariantB variant_b = 2; + VariantC variant_c = 3; + UnitVariant unit_variant = 4; + } +} + +message VariantA { + int32 value = 1; +} + +message VariantB { + string value = 1; +} + +message VariantC { + int32 x = 1; + double y = 2; +} + +// Unit variant (empty) +message UnitVariant {} \ No newline at end of file diff --git a/suite/typescript/ts/bench.ts b/suite/typescript/ts/bench.ts new file mode 100644 index 000000000..46cedef69 --- /dev/null +++ b/suite/typescript/ts/bench.ts @@ -0,0 +1,64 @@ +import { Bench } from "tinybench" +import * as ProtobufRegistry from "./proto/main.ts" +import * as BincodeRegistry from "./bincode/registry.ts" +import * as Data from "./data.ts" + +const ComplexStruct_pb_obj: ProtobufRegistry.ComplexStruct = { + inner: { a: 42, b: "Hello" }, + flag: true, + items: [ + { variant: { $case: "variant_a", variant_a: { value: 10 } } }, + { variant: { $case: "variant_b", variant_b: { value: "World" } } }, + ], + unit: {}, + newtype: 99, + tuple: { first: 123, second: 45.67, third: "Test" }, + // @ts-ignore + map: { 3: 7n } +} + +await async function bench_encode() { + let b = new Bench({ time: 1_500 }) + + b.add("serdegen-bincode:encode", () => { + BincodeRegistry.ComplexStruct.encode(Data.ComplexStruct_obj) + }) + + b.add("JSON:encode", () => { + JSON.stringify(Data.ComplexStruct_obj) + }) + + b.add("protobuf-js-ts-proto:encode", () => { + ProtobufRegistry.ComplexStruct.encode(ComplexStruct_pb_obj) + }) + + await b.warmup() + await b.run() + + console.table(b.table()) +}() + +await async function bench_decode() { + let b = new Bench({ time: 1_500 }) + + let bincodec_encoded = BincodeRegistry.ComplexStruct.encode(Data.ComplexStruct_obj) + b.add("serdegen-bincode:decode", () => { + BincodeRegistry.ComplexStruct.decode(bincodec_encoded) + }) + + let json_encoded = JSON.stringify(Data.ComplexStruct_obj) + b.add("JSON:decode", () => { + JSON.parse(json_encoded) + }) + + let pb_encoded = ProtobufRegistry.ComplexStruct.encode(ComplexStruct_pb_obj).finish() + b.add("protobuf-js-ts-proto:decode", () => { + ProtobufRegistry.ComplexStruct.decode(pb_encoded) + }) + + await b.warmup() + await b.run() + + console.table(b.table()) +}() + diff --git a/suite/typescript/ts/bincode/bincode.ts b/suite/typescript/ts/bincode/bincode.ts new file mode 120000 index 000000000..6e360e71e --- /dev/null +++ b/suite/typescript/ts/bincode/bincode.ts @@ -0,0 +1 @@ +/home/foxpro/misc/sources/serde-reflection/serde-generate/runtime/typescript/bincode.ts \ No newline at end of file diff --git a/suite/typescript/ts/bincode/registry.ts b/suite/typescript/ts/bincode/registry.ts new file mode 100644 index 000000000..a545c617c --- /dev/null +++ b/suite/typescript/ts/bincode/registry.ts @@ -0,0 +1,183 @@ +import type * as $t from "./serde.ts" +import { BincodeReader, BincodeWriter } from "./bincode.ts" + +export type ComplexStruct = { + inner: SimpleStruct, + flag: $t.bool, + items: $t.Seq, + unit: UnitStruct, + newtype: NewtypeStruct, + tuple: TupleStruct, + tupple_inline: $t.Tuple<[$t.str, $t.i32]>, + map: $t.Map<$t.i32, $t.i64>, +} + +export type MultiEnum = + | { $: "variant_a", $0: $t.i32 } + | { $: "variant_b", $0: $t.str } + | { $: "variant_c", x: $t.u8, y: $t.f64 } + | { $: "unit_variant", $0?: $t.unit } + +export type NewtypeStruct = $t.i32 + +export type SimpleStruct = { + a: $t.u32, + b: $t.str, +} + +export type TupleStruct = $t.Tuple<[$t.i32, $t.f64, $t.str]> + +export type UnitStruct = $t.unit + +export const ComplexStruct = { + encode(value: ComplexStruct, writer = new BincodeWriter()) { + SimpleStruct.encode(value.inner, writer) + writer.write_bool(value.flag) + writer.write_length(value.items.length) + for (let item of value.items) { + MultiEnum.encode(item, writer) + } + UnitStruct.encode(value.unit, writer) + NewtypeStruct.encode(value.newtype, writer) + TupleStruct.encode(value.tuple, writer) + writer.write_string(value.tupple_inline.$0) + writer.write_i32(value.tupple_inline.$1) + writer.write_map(value.map, writer.write_i32.bind(writer), writer.write_i64.bind(writer)) + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: ComplexStruct = { + inner: SimpleStruct.decode(input, reader), + flag: reader.read_bool(), + items: reader.read_list(() => MultiEnum.decode(input, reader)), + unit: UnitStruct.decode(input, reader), + newtype: NewtypeStruct.decode(input, reader), + tuple: TupleStruct.decode(input, reader), + tupple_inline: { + $0: reader.read_string(), + $1: reader.read_i32(), + }, + map: reader.read_map<$t.i32, $t.i64>(reader.read_i32.bind(reader), reader.read_i64.bind(reader)), + } + return value + } +} + +export const MultiEnum = { + encode(value: MultiEnum, writer = new BincodeWriter()) { + switch (value.$) { + case "variant_a": { + writer.write_variant_index(0) + writer.write_i32(value.$0) + break + } + case "variant_b": { + writer.write_variant_index(1) + writer.write_string(value.$0) + break + } + case "variant_c": { + writer.write_variant_index(2) + writer.write_u8(value.x) + writer.write_f64(value.y) + break + } + case "unit_variant": { + writer.write_variant_index(3) + writer.write_unit(value.$0) + break + } + } + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: MultiEnum + switch (reader.read_variant_index()) { + case 0: { + value = { + $: "variant_a", + $0: reader.read_i32(), + } satisfies Extract + break + } + case 1: { + value = { + $: "variant_b", + $0: reader.read_string(), + } satisfies Extract + break + } + case 2: { + value = { + $: "variant_c", + x: reader.read_u8(), + y: reader.read_f64(), + } satisfies Extract + break + } + case 3: { + value = { + $: "unit_variant", + $0: reader.read_unit() + } satisfies Extract + break + } + } + + return value + } +} + +export const NewtypeStruct = { + encode(value: NewtypeStruct, writer = new BincodeWriter()) { + writer.write_i32(value) + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: NewtypeStruct = reader.read_i32() + return value + } +} + +export const SimpleStruct = { + encode(value: SimpleStruct, writer = new BincodeWriter()) { + writer.write_u32(value.a) + writer.write_string(value.b) + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: SimpleStruct = { + a: reader.read_u32(), + b: reader.read_string(), + } + return value + } +} + +export const TupleStruct = { + encode(value: TupleStruct, writer = new BincodeWriter()) { + writer.write_i32(value.$0) + writer.write_f64(value.$1) + writer.write_string(value.$2) + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: TupleStruct = { + $0: reader.read_i32(), + $1: reader.read_f64(), + $2: reader.read_string(), + } + return value + } +} + +export const UnitStruct = { + encode(value: UnitStruct, writer = new BincodeWriter()) { + writer.write_unit(null) + return writer.get_bytes() + }, + decode(input: Uint8Array, reader = new BincodeReader(input)) { + let value: $t.unit = reader.read_unit() + return value + } +} diff --git a/suite/typescript/ts/bincode/serde.ts b/suite/typescript/ts/bincode/serde.ts new file mode 120000 index 000000000..726bbb790 --- /dev/null +++ b/suite/typescript/ts/bincode/serde.ts @@ -0,0 +1 @@ +/home/foxpro/misc/sources/serde-reflection/serde-generate/runtime/typescript/serde.ts \ No newline at end of file diff --git a/suite/typescript/ts/data.ts b/suite/typescript/ts/data.ts new file mode 100644 index 000000000..9d67c2650 --- /dev/null +++ b/suite/typescript/ts/data.ts @@ -0,0 +1,28 @@ +import * as Registry from "./bincode/registry.ts" + +export const SimpleStruct_obj: Registry.SimpleStruct = { a: 42, b: "Hello" } +export const SimpleStruct_bin = Uint8Array.from([42, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111]) + +export const MultiEnum_VariantC_obj: Registry.MultiEnum = { + $: "variant_c", + x: 5, y: 3.14 +} +export const MultiEnum_VariantC_bin = Uint8Array.from([2, 0, 0, 0, 5, 31, 133, 235, 81, 184, 30, 9, 64]) + +export const MultiEnum_Unit_obj: Registry.MultiEnum = { $: "unit_variant", $0: null } +export const MultiEnum_Unit_bin = Uint8Array.from([3, 0, 0, 0]) + +export const ComplexStruct_obj: Registry.ComplexStruct = { + inner: { a: 42, b: "Hello" }, + flag: true, + items: [ + { $: "variant_a", $0: 10 }, + { $: "variant_b", $0: "World" } + ], + unit: null, + newtype: 99, + tuple: { $0: 123, $1: 45.67, $2: "Test" }, + tupple_inline: { $0: "SomeString", $1: 777 }, + map: new Map().set(3, 7n) +} +export const ComplexStruct_bin = Uint8Array.from([42, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 87, 111, 114, 108, 100, 99, 0, 0, 0, 123, 0, 0, 0, 246, 40, 92, 143, 194, 213, 70, 64, 4, 0, 0, 0, 0, 0, 0, 0, 84, 101, 115, 116, 10, 0, 0, 0, 0, 0, 0, 0, 83, 111, 109, 101, 83, 116, 114, 105, 110, 103, 9, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0]) diff --git a/suite/typescript/ts/proto/main.ts b/suite/typescript/ts/proto/main.ts new file mode 100644 index 000000000..e302b8412 --- /dev/null +++ b/suite/typescript/ts/proto/main.ts @@ -0,0 +1,765 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v1.181.2 +// protoc v5.28.3 +// source: main.proto + +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal.js"; + +export const protobufPackage = ""; + +export interface UnitStruct { +} + +export interface NewtypeStruct { + value: number; +} + +export interface TupleStruct { + first: number; + second: number; + third: string; +} + +export interface SimpleStruct { + a: number; + b: string; +} + +export interface ComplexStruct { + inner: SimpleStruct | undefined; + flag: boolean; + items: MultiEnum[]; + unit: UnitStruct | undefined; + newtype: number; + tuple: TupleStruct | undefined; + map: { [key: number]: number }; +} + +export interface ComplexStruct_MapEntry { + key: number; + value: number; +} + +export interface MultiEnum { + variant?: + | { $case: "variant_a"; variant_a: VariantA } + | { $case: "variant_b"; variant_b: VariantB } + | { $case: "variant_c"; variant_c: VariantC } + | { $case: "unit_variant"; unit_variant: UnitVariant } + | undefined; +} + +export interface VariantA { + value: number; +} + +export interface VariantB { + value: string; +} + +export interface VariantC { + x: number; + y: number; +} + +/** Unit variant (empty) */ +export interface UnitVariant { +} + +function createBaseUnitStruct(): UnitStruct { + return {}; +} + +export const UnitStruct = { + encode(_: UnitStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): UnitStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUnitStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): UnitStruct { + return UnitStruct.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): UnitStruct { + const message = createBaseUnitStruct(); + return message; + }, +}; + +function createBaseNewtypeStruct(): NewtypeStruct { + return { value: 0 }; +} + +export const NewtypeStruct = { + encode(message: NewtypeStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.value !== 0) { + writer.uint32(8).int32(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): NewtypeStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseNewtypeStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.value = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): NewtypeStruct { + return NewtypeStruct.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): NewtypeStruct { + const message = createBaseNewtypeStruct(); + message.value = object.value ?? 0; + return message; + }, +}; + +function createBaseTupleStruct(): TupleStruct { + return { first: 0, second: 0, third: "" }; +} + +export const TupleStruct = { + encode(message: TupleStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.first !== 0) { + writer.uint32(8).int32(message.first); + } + if (message.second !== 0) { + writer.uint32(17).double(message.second); + } + if (message.third !== "") { + writer.uint32(26).string(message.third); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): TupleStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTupleStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.first = reader.int32(); + continue; + case 2: + if (tag !== 17) { + break; + } + + message.second = reader.double(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.third = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): TupleStruct { + return TupleStruct.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TupleStruct { + const message = createBaseTupleStruct(); + message.first = object.first ?? 0; + message.second = object.second ?? 0; + message.third = object.third ?? ""; + return message; + }, +}; + +function createBaseSimpleStruct(): SimpleStruct { + return { a: 0, b: "" }; +} + +export const SimpleStruct = { + encode(message: SimpleStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.a !== 0) { + writer.uint32(8).int32(message.a); + } + if (message.b !== "") { + writer.uint32(18).string(message.b); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): SimpleStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSimpleStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.a = reader.int32(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.b = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): SimpleStruct { + return SimpleStruct.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SimpleStruct { + const message = createBaseSimpleStruct(); + message.a = object.a ?? 0; + message.b = object.b ?? ""; + return message; + }, +}; + +function createBaseComplexStruct(): ComplexStruct { + return { inner: undefined, flag: false, items: [], unit: undefined, newtype: 0, tuple: undefined, map: {} }; +} + +export const ComplexStruct = { + encode(message: ComplexStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.inner !== undefined) { + SimpleStruct.encode(message.inner, writer.uint32(10).fork()).ldelim(); + } + if (message.flag !== false) { + writer.uint32(16).bool(message.flag); + } + for (const v of message.items) { + MultiEnum.encode(v!, writer.uint32(26).fork()).ldelim(); + } + if (message.unit !== undefined) { + UnitStruct.encode(message.unit, writer.uint32(34).fork()).ldelim(); + } + if (message.newtype !== 0) { + writer.uint32(40).int32(message.newtype); + } + if (message.tuple !== undefined) { + TupleStruct.encode(message.tuple, writer.uint32(50).fork()).ldelim(); + } + Object.entries(message.map).forEach(([key, value]) => { + ComplexStruct_MapEntry.encode({ key: key as any, value }, writer.uint32(58).fork()).ldelim(); + }); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ComplexStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseComplexStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.inner = SimpleStruct.decode(reader, reader.uint32()); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.flag = reader.bool(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.items.push(MultiEnum.decode(reader, reader.uint32())); + continue; + case 4: + if (tag !== 34) { + break; + } + + message.unit = UnitStruct.decode(reader, reader.uint32()); + continue; + case 5: + if (tag !== 40) { + break; + } + + message.newtype = reader.int32(); + continue; + case 6: + if (tag !== 50) { + break; + } + + message.tuple = TupleStruct.decode(reader, reader.uint32()); + continue; + case 7: + if (tag !== 58) { + break; + } + + const entry7 = ComplexStruct_MapEntry.decode(reader, reader.uint32()); + if (entry7.value !== undefined) { + message.map[entry7.key] = entry7.value; + } + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): ComplexStruct { + return ComplexStruct.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ComplexStruct { + const message = createBaseComplexStruct(); + message.inner = (object.inner !== undefined && object.inner !== null) + ? SimpleStruct.fromPartial(object.inner) + : undefined; + message.flag = object.flag ?? false; + message.items = object.items?.map((e) => MultiEnum.fromPartial(e)) || []; + message.unit = (object.unit !== undefined && object.unit !== null) + ? UnitStruct.fromPartial(object.unit) + : undefined; + message.newtype = object.newtype ?? 0; + message.tuple = (object.tuple !== undefined && object.tuple !== null) + ? TupleStruct.fromPartial(object.tuple) + : undefined; + message.map = Object.entries(object.map ?? {}).reduce<{ [key: number]: number }>((acc, [key, value]) => { + if (value !== undefined) { + acc[globalThis.Number(key)] = globalThis.Number(value); + } + return acc; + }, {}); + return message; + }, +}; + +function createBaseComplexStruct_MapEntry(): ComplexStruct_MapEntry { + return { key: 0, value: 0 }; +} + +export const ComplexStruct_MapEntry = { + encode(message: ComplexStruct_MapEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== 0) { + writer.uint32(8).int32(message.key); + } + if (message.value !== 0) { + writer.uint32(16).int64(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ComplexStruct_MapEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseComplexStruct_MapEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.key = reader.int32(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.value = longToNumber(reader.int64() as Long); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): ComplexStruct_MapEntry { + return ComplexStruct_MapEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ComplexStruct_MapEntry { + const message = createBaseComplexStruct_MapEntry(); + message.key = object.key ?? 0; + message.value = object.value ?? 0; + return message; + }, +}; + +function createBaseMultiEnum(): MultiEnum { + return { variant: undefined }; +} + +export const MultiEnum = { + encode(message: MultiEnum, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + switch (message.variant?.$case) { + case "variant_a": + VariantA.encode(message.variant.variant_a, writer.uint32(10).fork()).ldelim(); + break; + case "variant_b": + VariantB.encode(message.variant.variant_b, writer.uint32(18).fork()).ldelim(); + break; + case "variant_c": + VariantC.encode(message.variant.variant_c, writer.uint32(26).fork()).ldelim(); + break; + case "unit_variant": + UnitVariant.encode(message.variant.unit_variant, writer.uint32(34).fork()).ldelim(); + break; + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MultiEnum { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMultiEnum(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.variant = { $case: "variant_a", variant_a: VariantA.decode(reader, reader.uint32()) }; + continue; + case 2: + if (tag !== 18) { + break; + } + + message.variant = { $case: "variant_b", variant_b: VariantB.decode(reader, reader.uint32()) }; + continue; + case 3: + if (tag !== 26) { + break; + } + + message.variant = { $case: "variant_c", variant_c: VariantC.decode(reader, reader.uint32()) }; + continue; + case 4: + if (tag !== 34) { + break; + } + + message.variant = { $case: "unit_variant", unit_variant: UnitVariant.decode(reader, reader.uint32()) }; + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): MultiEnum { + return MultiEnum.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MultiEnum { + const message = createBaseMultiEnum(); + if ( + object.variant?.$case === "variant_a" && + object.variant?.variant_a !== undefined && + object.variant?.variant_a !== null + ) { + message.variant = { $case: "variant_a", variant_a: VariantA.fromPartial(object.variant.variant_a) }; + } + if ( + object.variant?.$case === "variant_b" && + object.variant?.variant_b !== undefined && + object.variant?.variant_b !== null + ) { + message.variant = { $case: "variant_b", variant_b: VariantB.fromPartial(object.variant.variant_b) }; + } + if ( + object.variant?.$case === "variant_c" && + object.variant?.variant_c !== undefined && + object.variant?.variant_c !== null + ) { + message.variant = { $case: "variant_c", variant_c: VariantC.fromPartial(object.variant.variant_c) }; + } + if ( + object.variant?.$case === "unit_variant" && + object.variant?.unit_variant !== undefined && + object.variant?.unit_variant !== null + ) { + message.variant = { $case: "unit_variant", unit_variant: UnitVariant.fromPartial(object.variant.unit_variant) }; + } + return message; + }, +}; + +function createBaseVariantA(): VariantA { + return { value: 0 }; +} + +export const VariantA = { + encode(message: VariantA, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.value !== 0) { + writer.uint32(8).int32(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): VariantA { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVariantA(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.value = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): VariantA { + return VariantA.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): VariantA { + const message = createBaseVariantA(); + message.value = object.value ?? 0; + return message; + }, +}; + +function createBaseVariantB(): VariantB { + return { value: "" }; +} + +export const VariantB = { + encode(message: VariantB, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.value !== "") { + writer.uint32(10).string(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): VariantB { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVariantB(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.value = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): VariantB { + return VariantB.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): VariantB { + const message = createBaseVariantB(); + message.value = object.value ?? ""; + return message; + }, +}; + +function createBaseVariantC(): VariantC { + return { x: 0, y: 0 }; +} + +export const VariantC = { + encode(message: VariantC, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.x !== 0) { + writer.uint32(8).int32(message.x); + } + if (message.y !== 0) { + writer.uint32(17).double(message.y); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): VariantC { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVariantC(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.x = reader.int32(); + continue; + case 2: + if (tag !== 17) { + break; + } + + message.y = reader.double(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): VariantC { + return VariantC.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): VariantC { + const message = createBaseVariantC(); + message.x = object.x ?? 0; + message.y = object.y ?? 0; + return message; + }, +}; + +function createBaseUnitVariant(): UnitVariant { + return {}; +} + +export const UnitVariant = { + encode(_: UnitVariant, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): UnitVariant { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUnitVariant(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + create, I>>(base?: I): UnitVariant { + return UnitVariant.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): UnitVariant { + const message = createBaseUnitVariant(); + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] } + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function longToNumber(long: Long): number { + if (long.gt(globalThis.Number.MAX_SAFE_INTEGER)) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (long.lt(globalThis.Number.MIN_SAFE_INTEGER)) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return long.toNumber(); +} + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} diff --git a/suite/typescript/ts/test.ts b/suite/typescript/ts/test.ts new file mode 100644 index 000000000..282d74a30 --- /dev/null +++ b/suite/typescript/ts/test.ts @@ -0,0 +1,51 @@ +import { test, suite } from "node:test" +import * as assert from "node:assert/strict" + +import * as Registry from "./bincode/registry.ts" +import * as Data from "./data.ts" + +suite("encode", () => { + test("SimpleStruct", () => { + const simple_instance = Registry.SimpleStruct.encode(Data.SimpleStruct_obj) + assert.deepEqual(simple_instance, Data.SimpleStruct_bin) + }) + test("MultiEnum_VariantC", () => { + const enum_instance = Registry.MultiEnum.encode(Data.MultiEnum_VariantC_obj) + assert.deepEqual(enum_instance, Data.MultiEnum_VariantC_bin) + }) + test("MultiEnum unit variant", () => { + const unit_variant = Registry.MultiEnum.encode(Data.MultiEnum_Unit_obj) + assert.deepEqual(unit_variant, Data.MultiEnum_Unit_bin) + }) + test("ComplexStruct", () => { + const complex_instance = Registry.ComplexStruct.encode(Data.ComplexStruct_obj) + assert.deepEqual(complex_instance, Data.ComplexStruct_bin) + }) + + test("ComplexStruct", () => { + const complex_instance = Registry.ComplexStruct.encode(Data.ComplexStruct_obj) + assert.deepEqual(complex_instance, Data.ComplexStruct_bin) + }) + + test("MultiEnum unit variant", () => { + const bin = Registry.MultiEnum.encode({ $: "unit_variant" }) + assert.deepEqual(bin, Data.MultiEnum_Unit_bin) + }) +}) + +suite("decode", () => { + test("MultiEnum", () => { + const enum_instance = Registry.MultiEnum.decode(Data.MultiEnum_VariantC_bin) + assert.deepEqual(enum_instance, Data.MultiEnum_VariantC_obj) + }) + + test("MultiEnum unit variant", () => { + const unit_variant = Registry.MultiEnum.decode(Data.MultiEnum_Unit_bin) + assert.deepEqual(unit_variant, Data.MultiEnum_Unit_obj) + }) + + test("ComplexStruct", () => { + const complex_instance = Registry.ComplexStruct.decode(Data.ComplexStruct_bin) + assert.deepEqual(complex_instance, Data.ComplexStruct_obj) + }) +}) diff --git a/suite/typescript/tsconfig.json b/suite/typescript/tsconfig.json new file mode 100644 index 000000000..b5d6fb7a3 --- /dev/null +++ b/suite/typescript/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "erasableSyntaxOnly": true, + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "target": "ESNext", + "allowImportingTsExtensions": true, + "types": ["@types/node"], + } +} \ No newline at end of file