Finish auto-generated TypeScript bindings (ts feature)#4
Conversation
Squash of the feature/new-ts WIP (replace ts lib, better TS, metadata) rebased onto master. Original work by Aiden McClelland. Co-authored-by: helix-nine <267227783+helix-nine@users.noreply.github.com>
Makes the `ts` feature compile and produce serde-accurate TypeScript.
- Drop the runtime-`syn` SerdeTag hack; resolve the serde enum
representation (external/internal/adjacent/untagged) from visit-rs's
runtime AttributeMeta, and emit correct TS for every variant kind:
external `{"V":payload}` / "V" for unit, internal `{tag:"V"}&payload`,
adjacent `{tag:"V";content:payload}`, untagged bare payload.
- Add impl_ts_enum! so enums can opt in (parallels impl_ts_struct!).
- impl TS for Option<T> (T|null); honor serde skip/skip_serializing
(omit) and skip_serializing_if/default (optional `?:`) via field metadata.
- Map all integer widths to `number` (serde_json emits JSON numbers, not
bigint) and map keys to `{[key:string]:V}` (JSON keys are always strings).
- no_ts() children emit a well-formed `{_PARAMS:unknown;_RETURN:unknown}`
leaf instead of bare `unknown` (which poisoned the whole tree's inference).
- insert_definition no longer panics in release on name clashes.
- Gate the impl_ts_struct! use behind cfg(ts) so --no-default-features builds.
- Add TSVisitor::into_module + handler_bindings() to emit a complete .d.ts
module, plus examples/generate_ts.rs.
- Depend on the fixed visit-rs branch (features=["meta"]); revert to a
published release once dr-bonez/visit-rs#1 merges.
- Tests assert exact TS for structs, all four enum taggings, Option, maps,
named definitions, and a handler tree; verified end-to-end with tsc --strict.
eeb2809 to
29ac427
Compare
|
Rebased onto
Master's changes since the old branch point were preserved through the rebase: One decision to confirm: I kept Verified after rebase: |
- flatten: capture the inner type and emit it as a TS intersection member
(`{...own fields} & Inner`) once the object body is closed, matching serde's
field-splicing on the wire.
- transparent: emit the single field's type directly, with no object wrapper.
|
Added two more of the deferred gaps (commit 8a4ff57):
Both covered by new assertions in Still deferred (documented for follow-up):
One open design question for the callable-parent gap ( |
- A ParentHandler with a root_handler is itself callable at the empty method path: surface the root handler's return as the parent node's _RETURN (its params already equal the parent's _PARAMS, per root_handler's bound), and teach type-helpers.ts that an empty method resolves to the node's own _PARAMS/_RETURN (single segments recurse into the child with an empty method, unifying leaf and directly-callable parent children). - impl TS for Flat<A,B> as a TS object intersection (A & B), matching serde's merged-object wire format for inherited params. - Drop now-unneeded visit-rs trait imports (the VisitVariants derive is import-free as of visit-rs branch); bump the visit-rs git pin.
|
Closed two more gaps (commit 4addade):
Also dropped the Still deferred (one item): generic/lifetime enums in the derive. Digging in, this isn't a quick gap — it's a derive-wide rework: lifetime params fail for structs too, and the enum methods need hygienic |
visit-rs now supports generic enums (for 'static type params), so a generic RPC enum can be rendered to TS via impl_ts_enum! on a concrete instantiation.
|
Bumped the visit-rs pin to pick up generic enum support and added a generic-enum TS test (commit 82e8213): a generic RPC enum With this, all the previously-deferred gaps are closed (flatten, transparent, callable parents, Flat<A,B>, import-free derive, and generic enums). See dr-bonez/visit-rs#1 for the generic-enum derive work this depends on. |
Finishes the WIP
feature/new-tsso thetsfeature compiles and emits serde-accurate TypeScript. Driven by an adversarial review of both this branch andvisit-rs(the review found 38 confirmed issues).Companion PR (required): dr-bonez/visit-rs#1 — the metadata/rename fixes this depends on. This branch points
visit-rsat that fork branch withfeatures = ["meta"]; flip it back to a published crates.io release once visit-rs#1 merges + publishes (marked with a TODO inCargo.toml).Was broken
feature/new-tsdid not compile:src/ts.rs'sSerdeTagparsed serde attributes withsynat runtime (not a dependency), and enum serde-tagging was a// TODOstub. Even ignoring that, the enum path never emitted variant names,Optionhad noTSimpl,u64mapped tobigint, map keys used numeric/bigintindex signatures (the latter is illegal TS), and ano_ts()child emitted bareunknownthat poisoned inference for the whole tree.Changes
synSerdeTagwithenum_repr, which reads the representation from visit-rs's runtimeAttributeMeta.Variant::visitnow emits the correct shape for all four representations and every variant kind:{"V": payload}, and"V"for unit variants#[serde(tag)]:{"tag":"V"} & payload#[serde(tag, content)]:{"tag":"V"; "content": payload}(no content key for unit)nullfor unit)impl_ts_enum!macro so enums opt in (parallel toimpl_ts_struct!).Option<T>→T | null, plus field-metadata handling inNamed::visit: serdeskip/skip_serializingfields are dropped, andskip_serializing_if/defaultmake the key optional (field?:).number(serde_json emits JSON numbers, neverbigint). Maps:{[key: string]: V}regardless of key type (JSON object keys are always strings; also removes the illegalbigintindex signature).no_ts()children emit{_PARAMS:unknown;_RETURN:unknown}instead of bareunknown, so one opted-out child no longer fails theRpcHandlerconstraint and poison sibling inference.insert_definitionusesdebug_assert_eq!instead of a release-modepanic!on name clashes (library code).--no-default-featuresbuilds: gated theimpl_ts_struct!import andEmptyinvocation behindcfg(feature = "ts").TSVisitor::into_module(root_name)renders a full module (helpers +export typefor eachDEFINE+ the root alias), andhandler_bindings(&handler, name)builds it straight from a handler tree. Newexamples/generate_ts.rs.Tests
tests/ts_bindings.rsasserts the exact TS for: a renamed struct with Option/skip/skip_if/u64/Vec, all four enum representations (incl. unit/newtype/tuple/struct variants), maps, namedDEFINEreferences + module assembly, and a full handler tree (including ano_tschild). Verified end-to-end withtsc --strict: the generated module type-checks,RpcParamType/RpcReturnTyperesolve correctly, mistyped payloads are rejected, and ano_tschild does not poison siblings. All build configs green (default,--no-default-features [--features cbor]).Verified-but-deferred (from the review; not blockers for the feature)
#[serde(flatten)](needs hoisting inner fields via intersection) and#[serde(transparent)].Flat<A, B>: TS— only needed if a handler's ownParamsis aFlat(inherited params are represented through the parent chain intype-helpers.ts, so the common nested case works).ParentHandlerroot-handler (a directly-callable parent) is not yet surfaced in_CHILDREN.implgenerics and enum-variant-fieldskipin the derive (variant-field skip is handled here consumer-side via metadata).Note: enums used with
impl_ts_enum!currently needuse visit_rs::{Visit, EnumInfo};in scope (theVisitVariantsderive uses method-call syntax). Worth making the derive import-free in a visit-rs follow-up.