Skip to content

risevest/spectype

Repository files navigation

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/spectype

Quick 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 | off
import { 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       barrel

Design

  • Bring-your-own-client. client.ts ships a default axios.create()/ky.create() and a setClient(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 parse validates without changing the type or shape.
  • Response payload optional ⟺ null. For response/body properties, x?: T and x: T | null unify to x?: T | null. HTTP metadata (@query/@path/@header/@cookie) stays strict. Toggle with payloadOptionalNullable (default true).
  • Opt-in parsing, zero cost when off. parse: false (default) emits no schema imports and no parse? flag — consumers that never validate pay no module cost. Set parse: true to 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's validation branch (alongside http/network/timeout/fallback).
  • Pick your validator. validator: zod (v4), zod3, or valibot. The schema layer is fully behind one interface, so schemas.ts and 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>Error that dispatches by status (http), then network/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, or throw in fallback.
  • Bundler-friendly layout. generate.barrel: false skips the index.ts re-export and splits schemas.ts into 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.ts stays a single file (type-only, erased). The default (barrel: true) keeps the single schemas.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 + runtime

Tests 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.

About

A simple Typespec emitter

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages