TypeScript Build Setup
TypeScript erases type information at runtime. Aster’s aster-gen scanner
reads your source at build time via the TypeScript compiler API, discovers
all @Service, @WireType, @Rpc, @ServerStream, @ClientStream, and
@BidiStream decorated classes, and emits aster-rpc.generated.ts with
the metadata the runtime needs — field shapes, wire type mappings, method
signatures, and contract identity hashes.
This replaces the need to pass { request, response } to every decorator.
You write clean decorators; the scanner extracts types from the AST.
Install
Section titled “Install”npm install @aster-rpc/asteraster-gen ships as the aster-gen bin inside @aster-rpc/aster — no
extra package needed. TypeScript is a peer dependency (bring your own
version).
Run the scanner
Section titled “Run the scanner”# defaults: reads ./tsconfig.json, writes ./aster-rpc.generated.tsnpx aster-gen
# custom pathsnpx aster-gen -p tsconfig.app.json -o build/aster-rpc.generated.tsOr add it to your package.json:
{ "scripts": { "build": "aster-gen && tsc" }}Re-run after adding or changing wire types, RPC methods, or decorator options. The generated file is deterministic — same input always produces the same output.
Use in your app
Section titled “Use in your app”AsterServer.start() auto-imports aster-rpc.generated.js from the
working directory — no manual import or wiring needed:
import { AsterServer } from '@aster-rpc/aster';import { MyService } from './services.js';
const server = new AsterServer({ services: [new MyService()],});await server.start();await server.serve();If the generated file is missing, the runtime falls back to reflection and logs a warning.
What gets generated
Section titled “What gets generated”The output aster-rpc.generated.ts contains two exports:
WIRE_TYPES— ordered array of wire type descriptors (tag, fields, nested type refs, field name sets) in dependency order.SERVICES— array of service descriptors with method signatures, RPC patterns, pre-derived manifest fields, and BLAKE3 type hashes for cross-language contract identity.
The file is // @ts-nocheck and auto-formatted. Commit it or gitignore
it — either works. Committing makes the example runnable without a build
step; gitignoring keeps diffs clean.
Bundler plugins
Section titled “Bundler plugins”For projects using a bundler, plugins run aster-gen automatically on
build and during hot reload. When using a bundler plugin, pass the
generated option explicitly since dynamic import won’t resolve at
bundle time:
import { defineConfig } from 'vite';import { asterGen } from '@aster-rpc/aster/vite-plugin';
export default defineConfig({ plugins: [ asterGen({ project: 'tsconfig.json', out: 'aster-rpc.generated.ts', }), ],});The plugin runs on buildStart and watches for changes to decorated
source files during dev.
Webpack
Section titled “Webpack”import { AsterGenPlugin } from '@aster-rpc/aster/webpack-plugin';
export default { plugins: [ new AsterGenPlugin({ project: 'tsconfig.json', out: 'aster-rpc.generated.ts', }), ],};The plugin hooks into beforeCompile and re-scans when source files
change.
Type mapping
Section titled “Type mapping”The scanner maps TypeScript types to wire types per the Aster spec:
| TypeScript | Wire type |
|---|---|
string |
string |
boolean |
bool |
number |
float64 |
bigint |
int64 |
Date |
timestamp |
Uint8Array |
binary |
T[] / Array<T> |
list<T> |
Set<T> |
set<T> |
Map<K, V> |
map<K, V> |
T | null / T | undefined |
nullable<T> |
Class with @WireType |
ref (by tag) |
Branded numeric types
Section titled “Branded numeric types”Plain number maps to float64. For narrower types (int32, uint16, etc.),
use branded types:
import { i32, u64, f32 } from '@aster-rpc/aster';
@WireType("myapp/Metrics")class Metrics { count: i32 = 0; // → int32 total_bytes: u64 = 0n; // → uint64 avg_latency: f32 = 0; // → float32}The scanner detects branded types via their phantom property and maps them to the correct wire primitive.
Runtime fallback
Section titled “Runtime fallback”If aster-rpc.generated.js is not found (e.g. during rapid prototyping),
the runtime falls back to reflection: instantiating each wire type class
and inspecting Object.keys(). This works for simple cases but has
limitations:
- Empty arrays don’t reveal their element type.
- Optional/nullable nested types aren’t recursed.
- Non-default-constructible classes can’t be introspected.
- Contract identity hashes are zeroed (cross-language calls won’t match).
A console warning fires once per class on the fallback path. For
production use, always run aster-gen.