All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.2.0 - 2026-09-30
Backward compatible with 0.1. After the upgrade, run mix typelizer.gen: the route
runtime (routes/runtime.ts) and routes/index.ts get the new exports, so
mix typelizer.check reports them until they are regenerated. Route helpers with
long lines also wrap the way Prettier does now: an action without params whose
signature is longer than 80 columns, and a single-param route with a long param name
(about 30 characters or more), get a new line layout. Other generated files do not
change unless you use the new features.
Added
- URL defaults for route helpers:
setUrlDefaults()(an object, or a function read on every call) andaddUrlDefault()in the generated runtime, andconfig :typelizer, routes: [defaults: [:locale]]to make those params optional in the helper types. A route whose only required param is not the first one accepts its value directly (routes.article.show("intro")). A default that matches no path param of the router prints a warning. (#1) - Typed query params:
use Typelizer.Queryandquery :index, page: {:optional, :integer}, ...in a controller (or a LiveView) type thequeryoption of the route helper through a generatedTaskIndexQuerytype.:dateand:timeparams are strings; datetime params accept a string or aDate.RouteOptionsbecomesRouteOptions<Q>; its default keeps the 0.1 type. Actions of any arity may be declared (for controllers that overrideaction/2). (#2) Typelizer.Envelope:wrap/3(with akey_transform:option for snake_case serializers),paginated/2andcursor_paginated/2build{ data, meta }values; the type specs{:envelope, spec},{:envelope, spec, meta_spec},{:paginated, item}and{:cursor_paginated, item}generateEnvelope<T, M>,Paginated<T>andCursorPaginated<T>from a sharedEnvelope.ts, which is written only when a spec uses it. (#3)config :typelizer, validate_inertia_props: :valuesalso checks each rendered Inertia prop value against its declared type in dev and test (types, enums, nullability, serializer fields and nested serializers, envelopes) and reports each mismatch with a path such astasks[3].status.truekeeps the key-only check. Undeclarederrorsandflashprops are only checked to be maps (error bags nest). An unknownvalidate_inertia_propsvalue raises instead of turning the check off. (#4)
0.1.0 - 2026-09-30
Added
Typelizer.Serializer: a serializer DSL (attributes,attributewithtype:,nullable:andvalue:,has_one,has_many) that buildsserialize/2andserialize_many/2at compile time. Types are inferred from Ecto schemas, includingEcto.Enumliteral unions and embeds; Ecto:anybecomesunknown. Keys are camelCase by default (key_transform:), dates become ISO-8601 strings and decimals strings (or numbers withdecimal_type: :number). Mistakes are compile errors with a hint.required:on an embed attribute lists the embed fields that are never null (required: [:street, geo: [:lat, :lng]]orrequired: :all).optional: true(leave the key out when the value is nil) andif:(send the key only when a condition holds) onattribute,has_oneandhas_many. Both make the TypeScript key optional (key?:).- Type specs
{:union, [...]},{:intersection, [...]}and{:ts, text, [Serializer, ...]}(raw TypeScript that imports the named serializers). Unions, intersections and raw TypeScript with operators get parentheses inside lists. - Route helper generation from the Phoenix router: one typed helper per route
(
routes.task.show(id)→{ url, method }), typed path params, query strings in the Plug format (nested objects, arrays of objects, dates), encoded anchors,setRoutesBaseUrl()and path filters. - The generated TypeScript passes
tsc --strict --noUncheckedIndexedAccess --exactOptionalPropertyTypes --verbatimModuleSyntax; a test checks it on every run. Typelizer.InertiaPage:page/2declares the props of an Inertia page andshared/1the props shared by every page.Typelizer.InertiaPage.ValidateProps: a plug that raisesTypelizer.InertiaPage.PropsErrorin dev and test when a rendered page sends missing or undeclared props (config :typelizer, validate_inertia_props: true).mix typelizer.gen: writes one TypeScript interface per serializer, the route helpers and the Inertia page props (<component>.props.ts,SharedProps, aPagesmap). Deterministic output; stale generated files are removed; files without the typelizer header are never touched. Optional Prettier pass.mix typelizer.check: exits with status 1 and a short diff when the committed TypeScript is out of date.- Column nullability from PostgreSQL
information_schemawhenrepo:is set. - User guides for the public API: getting started, serializers, route helpers,
Inertia page props and configuration. They explain how
camelize_propschanges nested and data keys (andpreserve_case/1), and how to serialize custom Ecto types whose values are structs.