@risemaxi/spectype
v0.0.6
Published
TypeSpec emitter that generates TypeScript types, validation schemas, HTTP clients, and TanStack Query helpers.
Readme
spectype
Generate a thin, correct, learnable TypeScript client from TypeSpec — types, zod schemas, an HTTP client, and TanStack Query helpers. Bring your own axios/ky instance; the generated code imposes no base URL, auth, or interceptors.
Install
npm install --save-dev @risemaxi/spectypeQuick start
# tspconfig.yaml
emit:
- "@risemaxi/spectype"
options:
"@risemaxi/spectype":
emitter-output-dir: "{project-root}/tsp-output/client"
validator: zod # zod | zod3 | valibot
httpClient: axios # axios | ky
generate:
tanstack: react # react | offimport { setClient } from "./tsp-output/client/client.js";
import axios from "axios";
// optional — defaults to axios.create(); configure once if you need a baseURL/auth
setClient(axios.create({ baseURL: "https://api.example.com" }));
import { listItems, createItem, handleCreateItemError } from "./tsp-output/client/api/items.js";
const items = await listItems({ query: { limit: 20 } }); // JSON-deserialized, typed
try {
await createItem({ body: { name: "Widget" } });
} catch (e) {
handleCreateItemError(e, {
http: { 400: (body) => console.error(body) },
fallback: (err) => {
throw err;
},
});
}Generated layout
types.ts interfaces, unions (incl. @discriminator), enums, scalars
schemas.ts validation schemas (1:1 with types), non-transforming
client.ts default instance + setClient, error handler, form-data, path/query, paging helpers
api/<group>.ts per-group operations + TanStack Query options
index.ts barrelDesign
- Bring-your-own-client.
client.tsships a defaultaxios.create()/ky.create()and asetClient(instance); operations read the live binding (no per-call overhead). - JSON-deserialized, no surprises. Operations return the parsed JSON body; dates/
bytes/decimals stay strings. Schemas are non-transforming, so opt-in
parsevalidates without changing the type or shape. - Response payload optional ⟺ null. For response/body properties,
x?: Tandx: T | nullunify tox?: T | null. HTTP metadata (@query/@path/@header/@cookie) stays strict. Toggle withpayloadOptionalNullable(default true). - Opt-in parsing, zero cost when off.
parse: false(default) emits no schema imports and noparse?flag — consumers that never validate pay no module cost. Setparse: trueto generate the capability;parseDefault(default true) sets the per-call default, overridable with{ parse: false }. A failed parse throws and is routed to the error handler'svalidationbranch (alongsidehttp/network/timeout/fallback). - Pick your validator.
validator: zod(v4),zod3, orvalibot. The schema layer is fully behind one interface, soschemas.tsand the (synchronous) parse call are the only things that differ by library — everything else is shared. - Typed error handling that returns a result. Each op gets a
handle<Op>Errorthat dispatches by status (http), thennetwork/timeout/validation/fallback. Bodies are typed per status, and the call's result is inferred as the union of whatever your handlers return — so you can map an error to a value, orthrowinfallback. - Bundler-friendly layout.
generate.barrel: falseskips theindex.tsre-export and splitsschemas.tsinto one module per schema (schemas/<name>.ts), so a bundler that doesn't tree-shake within a module (e.g. Metro) pulls in only the schemas an import actually reaches. api files deep-import the consts they use;types.tsstays a single file (type-only, erased). The default (barrel: true) keeps the singleschemas.ts+index.ts. - Pagination. Standard TypeSpec
@list/@offset/@pageItems/… drive*InfiniteQueryOptions.
Options
| Option | Values | Default |
| ------------------------------- | -------------------------------------------------------- | --------- |
| validator | zod, zod3, valibot | zod |
| httpClient | axios, ky | axios |
| generate.types/schemas/client | boolean | true |
| generate.tanstack | react, off | off |
| generate.barrel | boolean | true |
| parse | boolean | false |
| parseDefault | boolean | true |
| decimalAs | string, number | string |
| unknownAs | unknown, any | unknown |
| payloadOptionalNullable | boolean | true |
| importExtension | js, none | js |
| publishable | { packageName, version, description, author, license } | — |
Development
bun install
bun run build # alloy build -> dist
bun test # golden snapshots + tsc-correctness + runtimeTests are functional: exact input→output goldens, real tsc type-checking of the
generated code, and runtime behavior — across {zod4, zod3, valibot} × {axios, ky}
and the option matrix.
