|
| 1 | +--- |
| 2 | +id: typescript-build-setup |
| 3 | +title: TypeScript Build Setup |
| 4 | +slug: /guides/typescript-build-setup |
| 5 | +--- |
| 6 | + |
| 7 | +TypeScript erases type information at runtime. Aster's `aster-gen` scanner |
| 8 | +reads your source at build time via the TypeScript compiler API, discovers |
| 9 | +all `@Service`, `@WireType`, `@Rpc`, `@ServerStream`, `@ClientStream`, and |
| 10 | +`@BidiStream` decorated classes, and emits `aster-rpc.generated.ts` with |
| 11 | +the metadata the runtime needs — field shapes, wire type mappings, method |
| 12 | +signatures, and contract identity hashes. |
| 13 | + |
| 14 | +This replaces the need to pass `{ request, response }` to every decorator. |
| 15 | +You write clean decorators; the scanner extracts types from the AST. |
| 16 | + |
| 17 | +## Install |
| 18 | + |
| 19 | +```bash |
| 20 | +npm install @aster-rpc/aster |
| 21 | +``` |
| 22 | + |
| 23 | +`aster-gen` ships as the `aster-gen` bin inside `@aster-rpc/aster` — no |
| 24 | +extra package needed. TypeScript is a peer dependency (bring your own |
| 25 | +version). |
| 26 | + |
| 27 | +## Run the scanner |
| 28 | + |
| 29 | +```bash |
| 30 | +# defaults: reads ./tsconfig.json, writes ./aster-rpc.generated.ts |
| 31 | +npx aster-gen |
| 32 | + |
| 33 | +# custom paths |
| 34 | +npx aster-gen -p tsconfig.app.json -o build/aster-rpc.generated.ts |
| 35 | +``` |
| 36 | + |
| 37 | +Or add it to your `package.json`: |
| 38 | + |
| 39 | +```json |
| 40 | +{ |
| 41 | + "scripts": { |
| 42 | + "build": "aster-gen && tsc" |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Re-run after adding or changing wire types, RPC methods, or decorator |
| 48 | +options. The generated file is deterministic — same input always produces |
| 49 | +the same output. |
| 50 | + |
| 51 | +## Use in your app |
| 52 | + |
| 53 | +`AsterServer.start()` auto-imports `aster-rpc.generated.js` from the |
| 54 | +working directory — no manual import or wiring needed: |
| 55 | + |
| 56 | +```typescript |
| 57 | +import { AsterServer } from '@aster-rpc/aster'; |
| 58 | +import { MyService } from './services.js'; |
| 59 | + |
| 60 | +const server = new AsterServer({ |
| 61 | + services: [new MyService()], |
| 62 | +}); |
| 63 | +await server.start(); |
| 64 | +await server.serve(); |
| 65 | +``` |
| 66 | + |
| 67 | +If the generated file is missing, the runtime falls back to reflection |
| 68 | +and logs a warning. |
| 69 | + |
| 70 | +## What gets generated |
| 71 | + |
| 72 | +The output `aster-rpc.generated.ts` contains two exports: |
| 73 | + |
| 74 | +- **`WIRE_TYPES`** — ordered array of wire type descriptors (tag, fields, |
| 75 | + nested type refs, field name sets) in dependency order. |
| 76 | +- **`SERVICES`** — array of service descriptors with method signatures, |
| 77 | + RPC patterns, pre-derived manifest fields, and BLAKE3 type hashes for |
| 78 | + cross-language contract identity. |
| 79 | + |
| 80 | +The file is `// @ts-nocheck` and auto-formatted. Commit it or gitignore |
| 81 | +it — either works. Committing makes the example runnable without a build |
| 82 | +step; gitignoring keeps diffs clean. |
| 83 | + |
| 84 | +## Bundler plugins |
| 85 | + |
| 86 | +For projects using a bundler, plugins run `aster-gen` automatically on |
| 87 | +build and during hot reload. When using a bundler plugin, pass the |
| 88 | +`generated` option explicitly since dynamic import won't resolve at |
| 89 | +bundle time: |
| 90 | + |
| 91 | +### Vite |
| 92 | + |
| 93 | +```typescript |
| 94 | +// vite.config.ts |
| 95 | +import { defineConfig } from 'vite'; |
| 96 | +import { asterGen } from '@aster-rpc/aster/vite-plugin'; |
| 97 | + |
| 98 | +export default defineConfig({ |
| 99 | + plugins: [ |
| 100 | + asterGen({ |
| 101 | + project: 'tsconfig.json', |
| 102 | + out: 'aster-rpc.generated.ts', |
| 103 | + }), |
| 104 | + ], |
| 105 | +}); |
| 106 | +``` |
| 107 | + |
| 108 | +The plugin runs on `buildStart` and watches for changes to decorated |
| 109 | +source files during dev. |
| 110 | + |
| 111 | +### Webpack |
| 112 | + |
| 113 | +```typescript |
| 114 | +// webpack.config.ts |
| 115 | +import { AsterGenPlugin } from '@aster-rpc/aster/webpack-plugin'; |
| 116 | + |
| 117 | +export default { |
| 118 | + plugins: [ |
| 119 | + new AsterGenPlugin({ |
| 120 | + project: 'tsconfig.json', |
| 121 | + out: 'aster-rpc.generated.ts', |
| 122 | + }), |
| 123 | + ], |
| 124 | +}; |
| 125 | +``` |
| 126 | + |
| 127 | +The plugin hooks into `beforeCompile` and re-scans when source files |
| 128 | +change. |
| 129 | + |
| 130 | +## Type mapping |
| 131 | + |
| 132 | +The scanner maps TypeScript types to wire types per the Aster spec: |
| 133 | + |
| 134 | +| TypeScript | Wire type | |
| 135 | +|------------|-----------| |
| 136 | +| `string` | `string` | |
| 137 | +| `boolean` | `bool` | |
| 138 | +| `number` | `float64` | |
| 139 | +| `bigint` | `int64` | |
| 140 | +| `Date` | `timestamp` | |
| 141 | +| `Uint8Array` | `binary` | |
| 142 | +| `T[]` / `Array<T>` | `list<T>` | |
| 143 | +| `Set<T>` | `set<T>` | |
| 144 | +| `Map<K, V>` | `map<K, V>` | |
| 145 | +| `T \| null` / `T \| undefined` | `nullable<T>` | |
| 146 | +| Class with `@WireType` | `ref` (by tag) | |
| 147 | + |
| 148 | +### Branded numeric types |
| 149 | + |
| 150 | +Plain `number` maps to `float64`. For narrower types (int32, uint16, etc.), |
| 151 | +use branded types: |
| 152 | + |
| 153 | +```typescript |
| 154 | +import { i32, u64, f32 } from '@aster-rpc/aster'; |
| 155 | + |
| 156 | +@WireType("myapp/Metrics") |
| 157 | +class Metrics { |
| 158 | + count: i32 = 0; // → int32 |
| 159 | + total_bytes: u64 = 0n; // → uint64 |
| 160 | + avg_latency: f32 = 0; // → float32 |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +The scanner detects branded types via their phantom property and maps them |
| 165 | +to the correct wire primitive. |
| 166 | + |
| 167 | +## Runtime fallback |
| 168 | + |
| 169 | +If `aster-rpc.generated.js` is not found (e.g. during rapid prototyping), |
| 170 | +the runtime falls back to reflection: instantiating each wire type class |
| 171 | +and inspecting `Object.keys()`. This works for simple cases but has |
| 172 | +limitations: |
| 173 | + |
| 174 | +- Empty arrays don't reveal their element type. |
| 175 | +- Optional/nullable nested types aren't recursed. |
| 176 | +- Non-default-constructible classes can't be introspected. |
| 177 | +- Contract identity hashes are zeroed (cross-language calls won't match). |
| 178 | + |
| 179 | +A console warning fires once per class on the fallback path. For |
| 180 | +production use, always run `aster-gen`. |
0 commit comments