Vow::Codegen
Constants
Crystal types that map to a TypeScript built-in — they don't need to be
captured as surface types, and they're the only non-serializable leaves
allowed to cross the boundary. Anything else that isn't
JSON::Serializable is rejected (see the else branch below). Kept in
sync with Vow::Codegen.crystal_to_ts.
Class methods
Maps a raw Crystal type string (as captured in the manifest) to a
TypeScript type, recursively. known maps a captured type's full Crystal
name to the TS interface name to use for it.
"Array(Int32)" -> "number[]" "Hash(String, Bool)" -> "Record<string, boolean>" "(Inner | Nil)" -> "Inner | null" "CounterState" (known) -> "CounterState"
An identifier that is neither a primitive nor in known raises
UnmappableType — it never falls back to any.
Renders a procedure's opaque opts bag as a JS/TS object literal the stub
passes to the transport — {} when empty, otherwise {"key": value, ...}
with quoted keys (so any opt name is valid) and each value emitted via
JSON::Any#to_json (valid JS for strings, numbers, and booleans alike).
Both targets call this so the .js runtime and its .d.ts stay byte-for-
byte aligned on opts.
Renders the namespace tree to a nested literal. leaf turns a
(segment, procedure) pair into one member line (no trailing separator);
type_mode switches the member separator and so the literal kind —
; for a TypeScript type literal (a .d.ts shape), , for a value
object literal (a .ts/.js runtime object). Branches recurse with the
same leaf, so all three targets share one walk.
Macros
Appends a Vow::TypeDescriptor to acc for every JSON::Serializable
type reachable from t, transitively, walking through generic type args
(Array(T), Hash(K, V), …), NamedTuple members (by key), and unions
(T?). seen is a |-joined
path of visited type names that prevents infinite recursion on
self-referential types; DAG diamonds may emit a type more than once, so
callers dedup by crystal_name.
The fail-loud contract lives in the else branch: a leaf that is neither
a built-in primitive nor JSON::Serializable can't honestly cross the
boundary, so we raise at compile time with a fix rather than let codegen
invent a type for it.
Unions are unwrapped inline at every recursion site (not delegated)
because interpolating a union TypeNode through a macro-call argument
renders it as a parenthesized expression that can no longer .resolve.