Skip to content
Merged
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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,20 @@ jobs:

- name: Git diff of regenerated files
run: cd _examples && make diff

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 9

- name: Unit tests (regenerate enum fixtures against current templates, then run vitest)
run: |
cd tests-unit
pnpm install --frozen-lockfile
pnpm generate:enum
pnpm test
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,79 @@ function UserProfile({ userId }) {

The query keys follow the pattern `[ServiceName, methodName, request?]` and are fully type-safe with `as const` assertions.

### Enum style: `enum` vs `union`

TypeScript best practices are moving away from `enum` declarations — they emit runtime
code that can't be erased, which is incompatible with the `erasableSyntaxOnly` tsconfig
option and tooling that expects type-only syntax to disappear at build time.

The `-enumStyle` option lets you choose how schema enums are code-generated:

| `-enumStyle` value | Output |
| ------------------ | ------ |
| `enum` (default) | Traditional TypeScript `export enum` |
| `union` | `as const` object + union type (erasable, no runtime `enum`) |

Given this schema:

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

enum Intent: string
- openSession
- closeSession
```

`-enumStyle=enum` (default) generates:

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

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

`-enumStyle=union` generates:

```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]
```

The `union` style produces a value (the `const` object) and a type that share the same
name, so it's *mostly* a drop-in replacement anywhere the enum was used — function
parameters, interface fields, and client/server code all reference the type by the same
identifier. You still get autocompletion and access to members via `Kind.USER`, and
nothing is left behind at runtime beyond a plain object.

There are two behavioral differences from a real `enum` to be aware of:

- **No reverse mapping for numeric enums.** A numeric TypeScript `enum` lets you look up a
member name by its value (`WebrpcErrorCodes[1000]` → `'Unauthorized'`). A `const` object
has no reverse entries, so `WebrpcErrorCodes[1000]` is `undefined`. Consumer code that maps
an error code back to its name this way must be updated. (Schema enums are unaffected — they
are generated with string values regardless of their backing type.)
- **Members can't be used directly as types.** With an `enum` you can write `type T = Kind.USER`.
In the `union` style `Kind.USER` is a value, so use `type T = typeof Kind.USER` instead.

Passing any value other than `enum` or `union` (e.g. `-enumStyle=foo`) prints an error and exits.

## Usage

```
Expand Down Expand Up @@ -68,6 +141,7 @@ Change any of the following values by passing `-option="Value"` CLI flag to `web
| `-server` | generate server code | `false` | v0.0.1 |
| `-webrpcHeader` | send Webrpc header in all HTTP requests | `true` | v0.15.0 |
| `-schemaHash=false` | don't emit schema hash + version consts | `true` | v0.28.0 |
| `-enumStyle` | enum codegen style: `enum` or `union` | `enum` | v0.29.0 |

**Note:** Generated code requires ES2022+ runtime environment.

Expand Down
23 changes: 23 additions & 0 deletions errors.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{- define "errors" -}}
{{- $webrpcErrors := .WebrpcErrors -}}
{{- $schemaErrors := .SchemaErrors -}}
{{- $opts := .Opts -}}

//
// Errors
Expand Down Expand Up @@ -58,6 +59,27 @@ export class {{$error.Name}}Error extends WebrpcError {
}
{{ end }}

{{- if eq $opts.enumStyle "union"}}
export const errors = {
{{- range $_, $error := $webrpcErrors}}
{{$error.Name}}: '{{$error.Name}}',
{{- end}}
{{- range $_, $error := $schemaErrors}}
{{$error.Name}}: '{{$error.Name}}',
{{- end}}
} as const
export type errors = (typeof errors)[keyof typeof errors]

export const WebrpcErrorCodes = {
{{- range $_, $error := $webrpcErrors}}
{{$error.Name}}: {{$error.Code}},
{{- end }}
{{- range $_, $error := $schemaErrors}}
{{$error.Name}}: {{$error.Code}},
{{- end }}
} as const
export type WebrpcErrorCodes = (typeof WebrpcErrorCodes)[keyof typeof WebrpcErrorCodes]
{{- else}}
export enum errors {
{{- range $_, $error := $webrpcErrors}}
{{$error.Name}} = '{{$error.Name}}',
Expand All @@ -75,6 +97,7 @@ export enum WebrpcErrorCodes {
{{$error.Name}} = {{$error.Code}},
{{- end }}
}
{{- end}}

export const webrpcErrorByCode: { [code: number]: any } = {
{{- range $_, $error := $webrpcErrors}}
Expand Down
6 changes: 6 additions & 0 deletions main.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{{- set $opts "compat" (ternary (in .Opts.compat "" "true") true false) -}}
{{- set $opts "webrpcHeader" (ternary (eq (default .Opts.webrpcHeader "true") "false") false true) -}}
{{- set $opts "schemaHash" (ternary (eq (default .Opts.schemaHash "true") "false") false true) -}}
{{- set $opts "enumStyle" (default .Opts.enumStyle "enum") -}}

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

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

{{- if ne .WebrpcVersion "v1" -}}
{{- stderrPrintf "%s generator error: unsupported Webrpc version %s\n" .WebrpcTarget .WebrpcVersion -}}
{{- exit 1 -}}
Expand Down
Loading
Loading