wiregen

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 17, 2026 License: GPL-2.0, GPL-3.0 Imports: 11 Imported by: 0

README

wiregen

Go Reference Go version Go Report Card Test coverage Mutation OpenSSF Best Practices OpenSSF Scorecard

Generate TypeScript interfaces, decoders, and an SSE registry from Go types via AST analysis.

wiregen is a standalone Go library that, given a set of registered Go types and enum definitions, emits fully-typed TypeScript: interface declarations, runtime decoder functions with validation, and an SSE event→decoder registry. It analyzes your Go source with go/packages + go/types + go/ast, so it carries doc comments through to JSDoc on the generated interfaces. Its only build-time dependency is golang.org/x/tools; nothing it produces is a runtime dependency of your app.

Install

go get github.com/cplieger/wiregen@latest

Usage

Create a registry with NewRegistry (functional options configure behavior knobs), then set payload data via the exported fields:

package main

import "github.com/cplieger/wiregen"

type Status string

type User struct {
	// ID is the user's unique identifier.
	ID     int    `json:"id"`
	Name   string `json:"name"`
	Status Status `json:"status"`
}

func main() {
	r := wiregen.NewRegistry(
		wiregen.WithValidatorsImport("./validators.js"),
		wiregen.WithBusImport("./bus.js"),
	)

	// PackagePaths is optional — derived from the registered types when omitted.
	// Types are registered by identity via TypeRef (no reflect.Type needed).
	r.Types = []wiregen.WireType{wiregen.TypeRef[User]()}
	// Enum Values are optional — auto-discovered from the type's const block.
	r.Enums = map[string]wiregen.EnumDef{"Status": {}}
	r.SSEEvents = []wiregen.SSERegEntry{
		{EventType: "user", TypeName: "User"},
	}

	if err := r.Generate("./wire"); err != nil {
		panic(err)
	}
}

The ID doc comment above becomes a /** ID is the user's unique identifier. */ JSDoc line on the generated User interface.

API

NewRegistry
func NewRegistry(opts ...Option) *Registry

Creates a *Registry with behavior configured via functional options. Payload data (types, enums, constants, mappings) is then assigned to the returned registry's exported fields.

Functional options
Option Description
WithValidatorsImport(v string) Required. Import path for the validators module.
WithBusImport(v string) Required (unless WithSelfContainedRegistry(true)). Import path for the bus module.
WithTypesImportPath(v string) Import path for the types file used in decoders (default: "./types.gen.js").
WithHeaderComment(v string) Header comment prepended to every generated file.
WithRegisterFuncName(v string) Function name imported from the bus module (default: "registerSSEDecoder").
WithRegistryFuncName(v string) Exported function name in the registry file (default: "registerAllSSEDecoders").
WithSelfContainedRegistry(v bool) Use a self-contained Map-based registry instead of importing from BusImport.
WithFilenames(types, decoders, registry, constants string) Override output filenames (pass "" to keep defaults).
Registry fields (payload data)

Payload types are set via exported fields after construction:

Field Type Description
PackagePaths []string Import paths the AST engine loads + parses. Optional — derived from the registered types' packages when omitted.
Types []WireType Go types to generate TS interfaces and decoders for. Register via TypeRef[T]().
Enums map[string]EnumDef Named string enums (keyed by Go type name). Values is optional — auto-discovered from the type's const block (source order) when omitted; explicit Values win.
EnumTSName map[string]string Override the TS name for an enum (Go name → TS name).
TSNameOverride map[string]string Override the TS interface name for a struct (Go name → TS name).
PathNameOverride map[string]string Override the decoder path segment for a type (keyed by TS name).
TypeMappings map[string]string Custom Go type → TS type overrides, keyed by full importpath.Type (e.g. "…/uuid.UUID""string").
DecoderMappings map[string]string Custom Go type → decoder helper name (full importpath.Type key). When set, the decoder emits a validation call instead of a bare cast.
DiscriminatorMap map[string]map[string]string Per-union discriminator→variant decoder mapping; emit a union decoder for a sealed-interface union (see below).
SSEEvents []SSERegEntry Maps SSE event type strings to registered struct names.
Constants []WireConst Integer constants to emit into a constants file.

Discriminated unions are declared in Go source with a directive on the sealed interface — //wiregen:union discriminator=type variants=A,B,C — which emits export type X = A | B | C. A runtime union decoder (disc: string, v: unknown) => X is emitted only when DiscriminatorMap[X] is set.

Methods
  • (*Registry).Generate(outDir string) error — writes all generated files to outDir. Does not write validators.ts by default (see GenerateValidators below).
  • (*Registry).GenerateTypes() string — returns types file content.
  • (*Registry).GenerateDecoders() string — returns decoders file content. Panics if ValidatorsImport is empty.
  • (*Registry).GenerateRegistry() string — returns registry file content. Panics if BusImport is empty and SelfContainedRegistry is false.
  • (*Registry).GenerateConstants() string — returns constants file content.
  • (*Registry).GenerateValidators() string — returns a starter validators.ts containing the full 11-function contract (asObject, asArray, reqStr, reqNum, reqBool, optStr, optNum, optBool, reqOneOf<T>, decodeArray<T>, decodeRecord<T>) plus the Decoder<T> type. The emitted file is consumer-editable (NOT stamped "DO NOT EDIT") — copy it once, then own it. Opt-in only: Generate(outDir) does not write validators.ts; consumers must explicitly call GenerateValidators() and write the result themselves.
Types
// WireType identifies a registered Go type by package path + name.
type WireType struct {
    PkgPath string
    Name    string
}

// TypeRef registers a type by identity (the only use of reflect — for the
// {PkgPath, Name} pair; the field walk is done from source via the AST).
func TypeRef[T any]() WireType

type WireConst struct {
    TSName string
    Value  int
}

type EnumDef struct{ Values []string }

type UnionDef struct {
    Discriminator string
    Variants      []string
}

type SSERegEntry struct {
    EventType string
    TypeName  string
}

Validators contract

The consumer's validators module (at ValidatorsImport) must export:

  • asObject(v: unknown, path: string): Record<string, unknown>
  • asArray(v: unknown, path: string): unknown[]
  • reqStr(o: Record<string, unknown>, key: string, path: string): string
  • reqNum(o: Record<string, unknown>, key: string, path: string): number
  • reqBool(o: Record<string, unknown>, key: string, path: string): boolean
  • optStr(o: Record<string, unknown>, key: string, path: string): string | undefined
  • optNum(o: Record<string, unknown>, key: string, path: string): number | undefined
  • optBool(o: Record<string, unknown>, key: string, path: string): boolean | undefined
  • reqOneOf<T extends string>(o: Record<string, unknown>, key: string, values: readonly T[], path: string): T
  • decodeArray<T>(v: unknown, decoder: Decoder<T>, path: string): T[]
  • decodeRecord<T>(v: unknown, decoder: Decoder<T>, path: string): Record<string, T>
  • type Decoder<T> = (v: unknown) => T

Behavior notes

  • Doc comments on registered structs and their fields are carried through to /** … */ JSDoc on the generated interfaces (the AST engine reads them from source).
  • Unexported fields are skipped (matching encoding/json behavior).
  • time.Time maps to string; json.RawMessage and interface{} map to unknown.
  • []byte maps to string (JSON encodes []byte as base64).
  • omitzero (Go 1.24+) is treated the same as omitempty — the field becomes optional.
  • json:",string" causes the field to be typed as string and decoded with reqStr/optStr, matching encoding/json's string-wrapping behavior for numbers and booleans.
  • Map keys are always string in generated TS because JSON object keys are strings regardless of the Go map key type.
  • Embedded structs are flattened into the embedding interface (matching encoding/json).
  • Generate writes types.gen.ts + decoders.gen.ts always; registry.gen.ts only when there are SSE events; constants.gen.ts only when there are constants.
  • PackagePaths defaults to the distinct packages of the registered types; set it explicitly only to load extra packages.
  • Enum Values are auto-discovered from the named type's const declarations (in source order) when left empty; provide them explicitly to override the set or order.

Unsupported by design

The following are intentionally not supported:

Feature Reason
Go generics (type parameters) The Go type system can't represent uninstantiated generic types here. Register concrete instantiations instead.
Nullable vs optional distinction T | null vs ?: — current consumers treat null and absent identically. Pointer/omitempty → optional only.
tstype struct tag hints TypeMappings provides the same escape hatch at the registry level.
Inline anonymous struct fields A field whose type is an inline struct { … } literal maps to unknown. Register it as a named type instead. (Embedded named structs are flattened, not unknown.)

License

GPL-3.0 — see LICENSE.

Documentation

Overview

Package wiregen generates TypeScript interfaces, decoders, and an SSE registry from Go struct types using go/packages + go/types + ast.Inspect. Consumers register types via the compile-time-safe TypeRef[T]() helper and invoke Generate to emit TS files.

Example
package main

import (
	"fmt"

	"github.com/cplieger/wiregen"
	"github.com/cplieger/wiregen/testdata/basic"
)

func main() {
	r := wiregen.NewRegistry(
		wiregen.WithValidatorsImport("./validators.js"),
		wiregen.WithBusImport("./bus.js"),
	)
	r.PackagePaths = []string{"github.com/cplieger/wiregen/testdata/basic"}
	r.Types = []wiregen.WireType{wiregen.TypeRef[basic.Address]()}
	r.SSEEvents = []wiregen.SSERegEntry{
		{EventType: "addr", TypeName: "Address"},
	}

	ts := r.GenerateTypes()
	fmt.Println(ts != "")
}
Output:
true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EnumDef

type EnumDef struct{ Values []string }

EnumDef defines a named string enum with its valid values.

type Option

type Option func(*options)

Option configures optional behavior knobs on a Registry.

func WithBusImport

func WithBusImport(v string) Option

WithBusImport sets the import path for the SSE bus module.

func WithFilenames

func WithFilenames(types, decoders, registry, constants string) Option

WithFilenames overrides the output filenames for generated files.

func WithHeaderComment

func WithHeaderComment(v string) Option

WithHeaderComment sets the header comment prepended to every generated file.

func WithRegisterFuncName

func WithRegisterFuncName(v string) Option

WithRegisterFuncName sets the function name imported from the bus module.

func WithRegistryFuncName

func WithRegistryFuncName(v string) Option

WithRegistryFuncName sets the exported function name in the registry file.

func WithSelfContainedRegistry

func WithSelfContainedRegistry(v bool) Option

WithSelfContainedRegistry enables self-contained registry mode.

func WithTypesImportPath

func WithTypesImportPath(v string) Option

WithTypesImportPath sets the import path used in decoders to reference types.

func WithValidatorsImport

func WithValidatorsImport(v string) Option

WithValidatorsImport sets the import path for the validators module.

type Registry

type Registry struct {
	Enums            map[string]EnumDef
	EnumTSName       map[string]string
	TSNameOverride   map[string]string
	PathNameOverride map[string]string
	TypeMappings     map[string]string
	DecoderMappings  map[string]string
	DiscriminatorMap map[string]map[string]string

	ValidatorsImport      string
	TypesFilename         string
	ConstantsFilename     string
	RegistryFilename      string
	DecodersFilename      string
	BusImport             string
	TypesImportPath       string
	HeaderComment         string
	RegisterFuncName      string
	RegistryFuncName      string
	Types                 []WireType
	PackagePaths          []string
	Constants             []WireConst
	SSEEvents             []SSERegEntry
	SelfContainedRegistry bool
	// contains filtered or unexported fields
}

Registry holds all type registrations for code generation.

func NewRegistry

func NewRegistry(opts ...Option) *Registry

NewRegistry creates a Registry with the given functional options applied.

func (*Registry) Generate

func (r *Registry) Generate(outDir string) error

Generate writes generated TS files to outDir using the AST engine.

Example

ExampleRegistry_Generate demonstrates basic wiregen usage.

package main

import (
	"fmt"

	"github.com/cplieger/wiregen"
	"github.com/cplieger/wiregen/testdata/basic"
)

func main() {
	r := wiregen.NewRegistry(
		wiregen.WithValidatorsImport("./validators.js"),
		wiregen.WithBusImport("./bus.js"),
	)
	r.PackagePaths = []string{"github.com/cplieger/wiregen/testdata/basic"}
	r.Types = []wiregen.WireType{
		wiregen.TypeRef[basic.Address](),
	}

	fmt.Println(r.GenerateTypes() != "")
}
Output:
true

func (*Registry) GenerateConstants

func (r *Registry) GenerateConstants() string

GenerateConstants returns the constants.gen.ts content as a string.

func (*Registry) GenerateDecoders

func (r *Registry) GenerateDecoders() string

GenerateDecoders returns the decoders.gen.ts content as a string.

func (*Registry) GenerateRegistry

func (r *Registry) GenerateRegistry() string

GenerateRegistry returns the registry.gen.ts content as a string.

func (*Registry) GenerateTypes

func (r *Registry) GenerateTypes() string

GenerateTypes returns the types.gen.ts content as a string.

func (*Registry) GenerateValidators added in v1.2.0

func (r *Registry) GenerateValidators() string

GenerateValidators returns the opt-in validators starter module as a string: the reference implementation of the "Validators contract" (the 11 helper functions the generated decoders import — asObject, asArray, reqStr, reqNum, reqBool, optStr, optNum, optBool, reqOneOf, decodeArray, decodeRecord — plus the Decoder<T> type alias).

Unlike the other Generate* methods, the content is constant (it does not depend on the registered types) and the module carries a distinct "copy once, then own it" banner instead of r.HeaderComment — it is a one-time scaffold a NEW consumer copies once and then OWNS and edits freely. It is never regenerated and is deliberately NOT part of Generate's default writes, so an existing consumer's hand-edited copy is never clobbered.

type SSERegEntry

type SSERegEntry struct {
	EventType string
	TypeName  string
}

SSERegEntry maps an SSE event type to a registered struct name.

type UnionDef

type UnionDef struct {
	Discriminator string
	Variants      []string
}

UnionDef defines a discriminated union parsed from //wiregen:union directive.

type WireConst

type WireConst struct {
	TSName string
	Value  int
}

WireConst defines a named integer constant to emit into TypeScript.

type WireType

type WireType struct {
	PkgPath string
	Name    string
}

WireType is a compile-time-safe type reference captured by TypeRef[T]().

func TypeRef

func TypeRef[T any]() WireType

TypeRef registers a concrete Go type for TS generation. A typo or nonexistent type is a compile error — the generic constraint ensures T exists.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL