Skip to content

Commit

Permalink
Improve TypeScript generated code (fixes and improvements) (#603)
Browse files Browse the repository at this point in the history
* Export enum options as objects/functions

Thanks to that devs could now use it more intuitively, similarly to
Rust, for example:

    let role = Role.Admin; // it returns { tag: "Admin", value: undefined };
    let role1 = Role.Custom("Foo") // returns { tag: "Custom", value: "Foo" };

* 64 bit ints need to be represented as BigInt, not number

In JS all numbers are 64bit floats, but that means integers are only up
to 53 bits. Thus any 64+ bits integers need to be handled as BigInts

* Use camel case for product names

* Don't automatically register reducers and components

* Improve reducers and components

* Use this.db for tables

* Fix u64 and i64 to be a BigInt

* Table proxy changes

* Clippy

* Remove some of the reducer/table methods from autogen

Some of the methods were moved to the parent classes

* Remove _tableProxy import

* Remove count() and all() from typescript generation

---------

Signed-off-by: Piotr Sarnacki <drogus@gmail.com>
  • Loading branch information
drogus authored Dec 9, 2023
1 parent 45ce5b4 commit 3061cee
Showing 1 changed file with 70 additions and 169 deletions.
239 changes: 70 additions & 169 deletions crates/cli/src/subcommands/generate/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ fn maybe_primitive(b: &BuiltinType) -> MaybePrimitive {
| BuiltinType::U16
| BuiltinType::I32
| BuiltinType::U32
| BuiltinType::I64
| BuiltinType::U64
| BuiltinType::F32
| BuiltinType::F64 => "number",
BuiltinType::I128 | BuiltinType::U128 => "BigInt",
BuiltinType::I128 | BuiltinType::U128 | BuiltinType::I64 | BuiltinType::U64 => "BigInt",
BuiltinType::String => "string",
BuiltinType::Array(ty) => return MaybePrimitive::Array(ty),
BuiltinType::Map(m) => return MaybePrimitive::Map(m),
Expand Down Expand Up @@ -86,8 +84,8 @@ fn typescript_as_type(b: &BuiltinType) -> &str {
BuiltinType::U16 => "Number",
BuiltinType::I32 => "Number",
BuiltinType::U32 => "Number",
BuiltinType::I64 => "Number",
BuiltinType::U64 => "Number",
BuiltinType::I64 => "BigInt",
BuiltinType::U64 => "BigInt",
BuiltinType::I128 => "BigInt",
BuiltinType::U128 => "BigInt",
BuiltinType::F32 => "Number",
Expand Down Expand Up @@ -261,10 +259,16 @@ fn convert_product_type<'a>(
for (_, elem) in product_type.elements.iter().enumerate() {
writeln!(
f,
"{INDENT}new ProductTypeElement({}, {}),",
"{INDENT}new ProductTypeElement(\"{}\", {}),",
elem.name
.to_owned()
.map(|s| format!("\"{s}\""))
.map(|s| {
if s == "__identity_bytes" {
s
} else {
typescript_field_name(s.to_case(Case::Camel))
}
})
.unwrap_or("null".into()),
convert_algebraic_type(ctx, &elem.algebraic_type, ref_prefix)
)?;
Expand Down Expand Up @@ -440,9 +444,24 @@ pub fn autogen_typescript_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> S
};
writeln!(
output,
"export type {variant_name} = {{ tag: \"{variant_name}\"; value: {a_type} }};"
"export type {variant_name} = {{ tag: \"{variant_name}\", value: {a_type} }};"
)
.unwrap();

// export an object or a function representing an enum value, so people
// can pass it as an argument
match variant.algebraic_type {
AlgebraicType::Product(_) => writeln!(
output,
"export const {variant_name} = {{ tag: \"{variant_name}\", value: undefined }};"
)
.unwrap(),
_ => writeln!(
output,
"export const {variant_name} = (value: {a_type}): {variant_name} => {{ return {{ tag: \"{variant_name}\", value }} }};"
)
.unwrap(),
};
}

writeln!(output).unwrap();
Expand Down Expand Up @@ -651,7 +670,7 @@ fn autogen_typescript_product_table_common(
writeln!(output).unwrap();

writeln!(output, "// @ts-ignore").unwrap();
writeln!(output, "import {{ __SPACETIMEDB__, AlgebraicType, ProductType, BuiltinType, ProductTypeElement, SumType, SumTypeVariant, IDatabaseTable, AlgebraicValue, ReducerEvent, Identity, Address }} from \"@clockworklabs/spacetimedb-sdk\";").unwrap();
writeln!(output, "import {{ __SPACETIMEDB__, AlgebraicType, ProductType, BuiltinType, ProductTypeElement, SumType, SumTypeVariant, DatabaseTable, AlgebraicValue, ReducerEvent, Identity, Address, ClientDB, SpacetimeDBClient }} from \"@clockworklabs/spacetimedb-sdk\";").unwrap();

let mut imports = Vec::new();
generate_imports(ctx, &product_type.elements, &mut imports, None);
Expand All @@ -663,11 +682,12 @@ fn autogen_typescript_product_table_common(

writeln!(output).unwrap();

writeln!(output, "export class {struct_name_pascal_case} extends IDatabaseTable").unwrap();
writeln!(output, "export class {struct_name_pascal_case} extends DatabaseTable").unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);

writeln!(output, "public static db: ClientDB = __SPACETIMEDB__.clientDB;").unwrap();
writeln!(output, "public static tableName = \"{struct_name_pascal_case}\";").unwrap();

let mut constructor_signature = Vec::new();
Expand Down Expand Up @@ -785,110 +805,11 @@ fn autogen_typescript_product_table_common(
);

writeln!(output).unwrap();

writeln!(
output,
"public static onInsert(callback: (value: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)"
)
.unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").onInsert(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();

writeln!(output, "public static onUpdate(callback: (oldValue: {struct_name_pascal_case}, newValue: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)").unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").onUpdate(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();

writeln!(
output,
"public static onDelete(callback: (value: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)"
)
.unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").onDelete(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();

writeln!(
output,
"public static removeOnInsert(callback: (value: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)"
)
.unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").removeOnInsert(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();

writeln!(output, "public static removeOnUpdate(callback: (oldValue: {struct_name_pascal_case}, newValue: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)").unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").removeOnUpdate(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();

writeln!(
output,
"public static removeOnDelete(callback: (value: {struct_name_pascal_case}, reducerEvent: ReducerEvent | undefined) => void)"
)
.unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);
writeln!(
output,
"__SPACETIMEDB__.clientDB.getTable(\"{struct_name_pascal_case}\").removeOnDelete(callback);"
)
.unwrap();
}
writeln!(output, "}}").unwrap();
writeln!(output).unwrap();
}
}
writeln!(output, "}}").unwrap();

writeln!(output, "\nexport default {struct_name_pascal_case};").unwrap();
writeln!(
output,
"\n__SPACETIMEDB__.registerComponent(\"{struct_name_pascal_case}\", {struct_name_pascal_case});"
)
.unwrap();

output.into_inner()
}
Expand Down Expand Up @@ -936,6 +857,7 @@ fn autogen_typescript_product_value_to_struct(
output.into_inner()
}

#[allow(dead_code)]
fn indented_block<R>(output: &mut CodeIndenter<String>, f: impl FnOnce(&mut CodeIndenter<String>) -> R) -> R {
writeln!(output, "{{").unwrap();
let res = f(&mut output.indented(1));
Expand All @@ -950,28 +872,6 @@ fn autogen_typescript_access_funcs_for_struct(
table_name: &str,
table: &TableSchema,
) {
writeln!(output, "public static count(): number").unwrap();
indented_block(output, |output| {
writeln!(
output,
"return __SPACETIMEDB__.clientDB.getTable(\"{table_name}\").count();",
)
.unwrap();
});

writeln!(output).unwrap();

writeln!(output, "public static all(): {table_name}[]").unwrap();
indented_block(output, |output| {
writeln!(
output,
"return __SPACETIMEDB__.clientDB.getTable(\"{table_name}\").getInstances() as unknown as {table_name}[];",
)
.unwrap();
});

writeln!(output).unwrap();

let constraints = table.column_constraints();
for col in table.columns() {
let is_unique = constraints[&NonEmpty::new(col.col_pos)].has_unique();
Expand Down Expand Up @@ -1036,7 +936,7 @@ fn autogen_typescript_access_funcs_for_struct(
}
writeln!(
output,
"for(let instance of __SPACETIMEDB__.clientDB.getTable(\"{table_name}\").getInstances())"
"for(let instance of this.db.getTable(\"{table_name}\").getInstances())"
)
.unwrap();
writeln!(output, "{{").unwrap();
Expand Down Expand Up @@ -1140,7 +1040,7 @@ pub fn autogen_typescript_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String
writeln!(output).unwrap();

writeln!(output, "// @ts-ignore").unwrap();
writeln!(output, "import {{ __SPACETIMEDB__, AlgebraicType, ProductType, BuiltinType, ProductTypeElement, IDatabaseTable, AlgebraicValue, ReducerArgsAdapter, SumTypeVariant, Serializer, Identity, Address, ReducerEvent }} from \"@clockworklabs/spacetimedb-sdk\";").unwrap();
writeln!(output, "import {{ __SPACETIMEDB__, AlgebraicType, ProductType, BuiltinType, ProductTypeElement, DatabaseTable, AlgebraicValue, ReducerArgsAdapter, SumTypeVariant, Serializer, Identity, Address, ReducerEvent, Reducer, SpacetimeDBClient }} from \"@clockworklabs/spacetimedb-sdk\";").unwrap();

let mut imports = Vec::new();
generate_imports(
Expand Down Expand Up @@ -1168,26 +1068,35 @@ pub fn autogen_typescript_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String
let arg_type_str = ty_fmt(ctx, &arg.algebraic_type, "");

func_arguments.push(format!("{arg_name}: {arg_type_str}"));
arg_names.push(format!("{}", serialize_type(ctx, &arg.algebraic_type, &arg_name, "")));
arg_names.push(arg_name.to_string());
}

writeln!(output, "export class {reducer_name_pascal_case}Reducer").unwrap();
let full_reducer_name = format!("{reducer_name_pascal_case}Reducer");

writeln!(output, "export class {full_reducer_name} extends Reducer").unwrap();
writeln!(output, "{{").unwrap();

{
indent_scope!(output);

writeln!(output, "public static call({})", func_arguments.join(", ")).unwrap();
writeln!(output, "{{").unwrap();
writeln!(
output,
"public static reducerName: string = \"{reducer_name_pascal_case}\";"
)
.unwrap();
writeln!(output, "public static call({}) {{", func_arguments.join(", ")).unwrap();
{
indent_scope!(output);

writeln!(output, "if (__SPACETIMEDB__.spacetimeDBClient) {{").unwrap();
writeln!(
output,
"const serializer = __SPACETIMEDB__.spacetimeDBClient.getSerializer();"
)
.unwrap();
writeln!(output, "this.getReducer().call({});", arg_names.join(", ")).unwrap();
}
writeln!(output, "}}\n").unwrap();

writeln!(output, "public call({}) {{", func_arguments.join(", ")).unwrap();
{
indent_scope!(output);

writeln!(output, "const serializer = this.client.getSerializer();").unwrap();

let mut arg_names = Vec::new();
for arg in reducer.args.iter() {
Expand All @@ -1204,12 +1113,7 @@ pub fn autogen_typescript_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String
arg_names.push(arg_name);
}

writeln!(
output,
"\t__SPACETIMEDB__.spacetimeDBClient.call(\"{func_name}\", serializer);"
)
.unwrap();
writeln!(output, "}}").unwrap();
writeln!(output, "this.client.call(\"{func_name}\", serializer);").unwrap();
}
// Closing brace for reducer
writeln!(output, "}}").unwrap();
Expand Down Expand Up @@ -1256,23 +1160,36 @@ pub fn autogen_typescript_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String
writeln!(output, "}}").unwrap();

writeln!(output).unwrap();

writeln!(
output,
"public static on(callback: (reducerEvent: ReducerEvent, {}) => void) {{",
func_arguments.join(", ")
)
.unwrap();
{
indent_scope!(output);

writeln!(output, "this.getReducer().on(callback);").unwrap();
}
writeln!(output, "}}").unwrap();

// OnCreatePlayerEvent(dbEvent.Status, Identity.From(dbEvent.CallerIdentity.ToByteArray()), args[0].ToObject<string>());
writeln!(
output,
"public static on(callback: (reducerEvent: ReducerEvent, reducerArgs: any[]) => void)"
"public on(callback: (reducerEvent: ReducerEvent, {}) => void)",
func_arguments.join(", ")
)
.unwrap();
writeln!(output, "{{").unwrap();
{
indent_scope!(output);

writeln!(output, "if (__SPACETIMEDB__.spacetimeDBClient) {{").unwrap();
writeln!(
output,
"\t__SPACETIMEDB__.spacetimeDBClient.on(\"reducer:{reducer_name_pascal_case}\", callback);"
"this.client.on(\"reducer:{reducer_name_pascal_case}\", callback);"
)
.unwrap();
writeln!(output, "}}").unwrap();
}

// Closing brace for Event parsing function
Expand All @@ -1283,22 +1200,6 @@ pub fn autogen_typescript_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String

writeln!(output).unwrap();

writeln!(
output,
"__SPACETIMEDB__.reducers.set(\"{reducer_name_pascal_case}\", {reducer_name_pascal_case}Reducer);"
)
.unwrap();

writeln!(output, "if (__SPACETIMEDB__.spacetimeDBClient) {{").unwrap();

{
indent_scope!(output);

writeln!(output, "__SPACETIMEDB__.spacetimeDBClient.registerReducer(\"{reducer_name_pascal_case}\", {reducer_name_pascal_case}Reducer);").unwrap();
}

writeln!(output, "}}").unwrap();

writeln!(output, "\nexport default {reducer_name_pascal_case}Reducer").unwrap();

output.into_inner()
Expand Down

0 comments on commit 3061cee

Please sign in to comment.