Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/superpowers/specs/2026-03-25-enum-union-types-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Configurable Enum Style: enum vs union/const

## Context

TypeScript best practices are moving away from `enum` declarations. The `erasableSyntaxOnly` tsconfig option reflects this trend. This change adds a `-enum` generator option so users can choose between traditional TypeScript enums and the modern `as const` object + union type pattern.

## Option

**`-enum`** accepts two values:

| Value | Description |
|-------|-------------|
| `enum` | (default) Current behavior — generates `export enum` |
| `union` | Generates `as const` object + union type |

## Output Formats

### Input RIDL

```ridl
enum Kind: uint32
- USER
- ADMIN

enum Intent: string
- openSession
- closeSession
```

### `-enum=enum` (default)

```typescript
export enum Kind {
USER = 'USER',
ADMIN = 'ADMIN'
}

export enum Intent {
openSession = 'openSession',
closeSession = 'closeSession'
}
```

### `-enum=union`

```typescript
export const Kind = {
USER: 'USER',
ADMIN: 'ADMIN',
} as const
export type Kind = (typeof Kind)[keyof typeof Kind]

export const Intent = {
openSession: 'openSession',
closeSession: 'closeSession',
} as const
export type Intent = (typeof Intent)[keyof typeof Intent]
```

## Design Details

- The const object and type share the same name — this is intentional and idiomatic TypeScript (declaration merging between value and type namespaces).
- Value logic is the same for both styles: `$field.Value` for string-typed enums, `$field.Name` for others.
- The union type is fully compatible everywhere a TypeScript enum would be used — function parameters, interface fields, etc. — because the type name is identical.
- No changes needed to type references in generated interfaces or client/server code.

## Files Modified

1. **`main.go.tmpl`** — Add `enum` option with default `"enum"`, validate accepts `"enum"` or `"union"`
2. **`types.go.tmpl`** — Branch on `$opts.enum`: existing block for `"enum"`, new const+union block for `"union"`
3. **`_examples/`** — Update generated examples

## Verification

1. Generate with `-enum=enum` — output should match current behavior exactly
2. Generate with `-enum=union` — output should use `as const` + union type pattern
3. Invalid `-enum=foo` — should print error and exit
4. No `-enum` flag — should default to `"enum"` style
6 changes: 6 additions & 0 deletions main.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{{- set $opts "server" (ternary (in .Opts.server "" "true") true false) -}}
{{- set $opts "compat" (ternary (in .Opts.compat "" "true") true false) -}}
{{- set $opts "webrpcHeader" (ternary (eq (default .Opts.webrpcHeader "true") "false") false true) -}}
{{- set $opts "enum" (default .Opts.enum "enum") -}}

{{- /* Print help on -help. */ -}}
{{- if exists .Opts "help" -}}
Expand All @@ -22,6 +23,11 @@
{{- end -}}
{{- end -}}

{{- if not (in $opts.enum "enum" "union") -}}
{{- stderrPrintf "-enum=%q is not supported, must be \"enum\" or \"union\"\n" $opts.enum -}}
{{- exit 1 -}}
{{- end -}}

{{- if ne .WebrpcVersion "v1" -}}
{{- stderrPrintf "%s generator error: unsupported Webrpc version %s\n" .WebrpcTarget .WebrpcVersion -}}
{{- exit 1 -}}
Expand Down
13 changes: 13 additions & 0 deletions types.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
{{range $_i, $type := $types -}}

{{if isEnumType $type }}
{{- if eq $opts.enum "union"}}
export const {{$type.Name}} = {
{{- range $i, $field := $type.Fields}}
{{- if eq $type.Type.Expr "string"}}
{{$field.Name}}: '{{$field.Value}}',
{{- else}}
{{$field.Name}}: '{{$field.Name}}',
{{- end}}
{{- end}}
} as const
export type {{$type.Name}} = (typeof {{$type.Name}})[keyof typeof {{$type.Name}}]
{{- else}}
export enum {{$type.Name}} {
{{- range $i, $field := $type.Fields}}
{{- if $i}},{{end}}
Expand All @@ -22,6 +34,7 @@ export enum {{$type.Name}} {
{{- end}}
{{- end}}
}
{{- end}}
{{end -}}

{{- if isStructType $type }}
Expand Down
Loading