Skip to content

TypeScript Types: struct ⇒ interface

The TypeScriptTypes rule demonstrates attribute-based triggering and recursive type mapping. It converts C++ structs to TypeScript export interface declarations, handling primitives, std::vector<T>, std::optional<T>, std::shared_ptr<T>, and std::map<K,V> recursively.

include/api_types.hpp
struct [[codegen::TypeScriptTypes]] UserProfile {
std::string username;
uint32_t age;
std::vector<std::string> roles;
std::optional<std::string> bio;
};

By default the output lands at api_types.TypeScriptTypes.ts next to the input header (the non-cpp default filename pattern is <stem>.<ruleName><ext>). To centralise the output, set outputDirectory and/or outputNameTemplate in the rule config.

api_types.TypeScriptTypes.ts
// Generated with Codegen - DO NOT EDIT!!!
// Auto-generated TypeScript types for rule: TypeScriptTypes
// Source:
export interface UserProfile {
username: string;
age: number;
roles: Array<string>;
bio: string | undefined;
}

The // Sources: lines in the example’s preamble iterate ctx.entities to list the contributing input files. Each entity in entities has its inputFile available, so the preamble can list the sources that contributed to this output file.

Run:

Terminal window
codegen -i include/api_types.hpp -r .codegen/rules -a TypeScriptTypes
C++ typeTypeScript type
boolboolean
int, int8_tint64_t, uint8_tuint64_t, float, double, size_tnumber
std::string, string, pathstring
std::vector<T>Array<T>
std::optional<T>T | undefined
std::shared_ptr<T>T | null
std::map<K,V>, std::unordered_map<K,V>Record<K, V>
Anything elseUsed as-is (assumed to be a TypeScript type reference)

The mapping is implemented as a recursive cppToTs(tSig) function in transform.luau. Adding new mappings means editing only that function.

Open transform.luau and add entries to the primitives table or new if name == "..." branches for generics:

-- Add std::set<T> -> Set<T>
if name == "set" then
local args = tSig.identifier.templateArguments or {}
if args[1] then return "Set<" .. cppToTs(args[1]) .. ">" end
return "Set<unknown>"
end

To consolidate every TypeScript interface into a single types.ts, add a grouping script and (optionally) override the output path:

grouping.luau
return function(input)
local data = json.decode(input)
local result = {}
for _, ent in ipairs(data.entities) do
result[tostring(ent.registryId)] = "generated/types.ts"
end
return json.encode(result)
end

The output path is resolved against the project root (the working directory).

Key Takeaways
  • Attribute annotation ([[codegen::TypeScriptTypes]]) triggers struct-level rules; no anchor needed.
  • Default non-cpp output: <stem>.<ruleName><ext> next to the input. Override with outputDirectory / outputNameTemplate.
  • Type mapping is a recursive LuaU function; extend it by adding branches.
  • Adding grouping.luau switches from 1:1 to fan-in routing.
  • Preamble runs once per (rule, outputFile) pair and receives the entities bound to that file, so it can conditionally emit content based on what entities land there.