Skip to content

Commit 3905bba

Browse files
rekhoffclockwork-labs-botbfops
authored
Regenerate case conversion TS bindings (#5434)
# Description of Changes Updates the TypeScript code generator to emit correct `.name()` values on table fields, and updates the three affected Case Conversion Test snapshots. **Problem:** For source identifiers with mixed/PascalCase casing (e.g., `Player1Id`), the in-process codegen path used by `cargo test` and `check-diff.sh` emitted `.name()` calls using the pre-conversion identifier (`name('Player1Id')`) instead of the canonical database column name (`name('player_1_id')`). The CLI `spacetime generate` path was already correct because its `extract-schema` round-trip canonicalizes names before codegen. The two paths disagreed, causing `check-diff` CI failures. **Root cause / Fix:** `write_object_type_builder_fields` in `crates/codegen/src/typescript.rs` was using the `TypespaceForGenerate` pre-conversion identifier for `.name()`. It now accepts an optional `&[ColumnDef]` and emits `column.name` for table fields. The camelCase accessor key (e.g., `player1Id`) is unchanged; only the underlying wire/database column name is corrected to snake_case. The three snapshot files in `crates/bindings-typescript/case-conversion-test-client/src/module_bindings/` are updated to match the corrected output. # API and ABI breaking changes No API or ABI changes at the Rust crate or C ABI level. For TypeScript users, this is a bug fix for the in-process generation path; the CLI path was already correct. # Expected complexity level and risk 1 - Trivial # Testing - [X] Local testing - Verified the in-process `cargo test -p spacetimedb-sdk -- case_conversion` path on `master` generated incorrect PascalCase `.name()` values (`name("Player1Id")`, missing `.name()` for `currentLevel2`/`status3Field`. - Verified the same in-process path on the PR branch generates correct snake_case `.name()` values (`name("player_1_id")`, `name("current_level_2")`, `name("status_3_field")`), matching the updated snapshots. - Verified the CLI `spacetime generate --lang typescript` path produces identical snake_case output on both `master` and the PR branch. - Created a standalone TypeScript test that confirms the generated bindings produce SQL with canonical snake_case column names (`"current_level_2"`, `"player_1_id"`, `"player_ref"`) via the SDK `toSql()` function. - [X] CI passing --------- Signed-off-by: Ryan <r.ekhoff@clockworklabs.io> Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com> Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
1 parent 77ed3b1 commit 3905bba

4 files changed

Lines changed: 34 additions & 19 deletions

File tree

crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_2_table.ts

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_at_level_2_table.ts

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-typescript/case-conversion-test-client/src/module_bindings/player_1_table.ts

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codegen/src/typescript.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use convert_case::{Case, Casing};
1515
use spacetimedb_lib::sats::layout::PrimitiveType;
1616
use spacetimedb_lib::sats::AlgebraicTypeRef;
1717
use spacetimedb_primitives::ColId;
18-
use spacetimedb_schema::def::{ConstraintDef, IndexDef, ModuleDef, ReducerDef, TableDef, TypeDef};
18+
use spacetimedb_schema::def::{ColumnDef, ConstraintDef, IndexDef, ModuleDef, ReducerDef, TableDef, TypeDef};
1919
use spacetimedb_schema::identifier::Identifier;
2020
use spacetimedb_schema::reducer_name::ReducerName;
2121
use spacetimedb_schema::schema::TableSchema;
@@ -85,7 +85,15 @@ impl Lang for TypeScript {
8585

8686
writeln!(out, "export default __t.row({{");
8787
out.indent(1);
88-
write_object_type_builder_fields(module, out, &product_def.elements, table.primary_key, true, true).unwrap();
88+
write_object_type_builder_fields(
89+
module,
90+
out,
91+
&product_def.elements,
92+
table.primary_key,
93+
true,
94+
Some(&table.columns),
95+
)
96+
.unwrap();
8997
out.dedent(1);
9098
writeln!(out, "}});");
9199
OutputFile {
@@ -143,7 +151,7 @@ impl Lang for TypeScript {
143151

144152
writeln!(out, "export const params = {{");
145153
out.with_indent(|out| {
146-
write_object_type_builder_fields(module, out, &procedure.params_for_generate.elements, None, true, false)
154+
write_object_type_builder_fields(module, out, &procedure.params_for_generate.elements, None, true, None)
147155
.unwrap()
148156
});
149157
writeln!(out, "}};");
@@ -815,7 +823,7 @@ fn define_body_for_reducer(module: &ModuleDef, out: &mut Indenter, params: &[(Id
815823
writeln!(out, "}};");
816824
} else {
817825
writeln!(out);
818-
out.with_indent(|out| write_object_type_builder_fields(module, out, params, None, true, false).unwrap());
826+
out.with_indent(|out| write_object_type_builder_fields(module, out, params, None, true, None).unwrap());
819827
writeln!(out, "}};");
820828
}
821829
}
@@ -840,7 +848,7 @@ fn define_body_for_product(
840848
writeln!(out, "}});");
841849
} else {
842850
writeln!(out);
843-
out.with_indent(|out| write_object_type_builder_fields(module, out, elements, None, true, false).unwrap());
851+
out.with_indent(|out| write_object_type_builder_fields(module, out, elements, None, true, None).unwrap());
844852
writeln!(out, "}});");
845853
}
846854
writeln!(out, "export type {name} = __Infer<typeof {name}>;");
@@ -932,7 +940,7 @@ fn write_object_type_builder_fields(
932940
elements: &[(Identifier, AlgebraicTypeUse)],
933941
primary_key: Option<ColId>,
934942
convert_case: bool,
935-
write_original_name: bool,
943+
columns: Option<&[ColumnDef]>,
936944
) -> anyhow::Result<()> {
937945
for (i, (ident, ty)) in elements.iter().enumerate() {
938946
let name = if convert_case {
@@ -945,7 +953,14 @@ fn write_object_type_builder_fields(
945953
Some(pk) => pk.idx() == i,
946954
None => false,
947955
};
948-
let original_name = (write_original_name && convert_case && *name != **ident).then_some(&**ident);
956+
// The `.name(..)` value is the in-database (canonical) column name, which may
957+
// differ from the generated camelCase accessor key. Emit it only when the
958+
// canonical name differs, so the client maps to the correct wire/column name
959+
// regardless of the source identifier's casing.
960+
let original_name = columns
961+
.and_then(|columns| columns.get(i))
962+
.map(|column| column.name.deref())
963+
.filter(|canonical| convert_case && *canonical != name.as_str());
949964
write_type_builder_field(module, out, &name, original_name, ty, is_primary_key)?;
950965
}
951966

@@ -1083,7 +1098,7 @@ fn define_body_for_sum(
10831098
(Identifier::for_test(pascal), ty.clone())
10841099
})
10851100
.collect();
1086-
out.with_indent(|out| write_object_type_builder_fields(module, out, &pascal_variants, None, false, false).unwrap());
1101+
out.with_indent(|out| write_object_type_builder_fields(module, out, &pascal_variants, None, false, None).unwrap());
10871102
writeln!(out, "}});");
10881103
writeln!(out, "export type {name} = __Infer<typeof {name}>;");
10891104
out.newline();

0 commit comments

Comments
 (0)