TypeScript

JAF components are HTML files, so there is no .tsx to type-check.

What the build gives you instead is generated declaration files describing your routes, components and unite() namespaces, which your editor and tsc pick up automatically.

The Generated Directory

Everything is written to src/generated/.

The directory is gitignored and regenerated on every build and on file changes in dev, so never edit it by hand.

File Module Generated by
routes.d.ts jaf:routes fileRouterPlugin() - on by default
namespaces.d.ts global augmentation namespaceCodegenPlugin() - opt in
components.d.ts jaf:components componentTypesCodegenPlugin() - see the note below

Make sure src/generated is inside your tsconfig.json include:

source
{
  "include": ["src", "src/generated"]
}

Route Types

Every route in pages/ gets a generated name and a params type.

The name is the URL path with slashes turned into hyphens and colons removed - / is home, /users/:id is users-id. Params are always string.

source
// src/generated/routes.d.ts (auto-generated)
declare module "jaf:routes" {
  export interface RouteMap {
    "home": {}
    "about": {}
    "blog-slug": { slug: string }
    "products-id": { id: string }
  }

  export type RouteName = keyof RouteMap
  export type RouteParams<T extends RouteName> = RouteMap[T]
  export type StaticRouteName = /* routes with no params */
  export type DynamicRouteName = Exclude<RouteName, StaticRouteName>
}

Import the types wherever you hold a route name:

source
import type { RouteName, RouteParams } from 'jaf:routes'

const defaultRoute: RouteName = 'home'          // checked
const params: RouteParams<'blog-slug'> = { slug: 'hello' }

They also type navigateTo(), which takes a route name rather than a path and checks the params against it:

source
import { navigateTo, generatePath } from 'jafjs/router'

navigateTo('users-id', { id: '123' })       // params checked against the route
generatePath('users-id', { id: '123' })     // -> '/users/123'

Most navigation is still an ordinary <a href>, which the router intercepts.

Typing Route State

window.route is declared globally and is reactive. It is not parameterised by RouteMap, so params come back as a plain record:

source
<script>
// Static snapshot
const id = window.route.params.id

// Derived: re-evaluates when the route changes
let slug = window.route.params.slug
</script>

The shape is { path, params, query, hash, name }. See Dynamic Routes.

Namespace Types for unite()

unite() reads a variable from another file by namespace path.

The namespace codegen plugin emits a global declaration of that tree so the path completes and the return type is inferred.

source
// vite.config.ts
import { namespaceCodegenPlugin } from 'jafjs/vite'

export default defineConfig({
  plugins: [
    // ...the standard JAF plugins
    namespaceCodegenPlugin(),
  ],
})

Namespaces follow the file path:

File Namespace
pages/demo.html demo
pages/admin/dashboard.html admin.dashboard
components/Button.html Button
components/ui/Modal.html ui.Modal
source
<script>
// Inferred as the declared type of `theme` in pages/demo.html
let theme = unite(demo.theme)
</script>

See unite().

Component Prop Types

The component codegen scans your .html components and emits a registry of their props:

source
// src/generated/components.d.ts (auto-generated)
declare module "jaf:components" {
  export interface ComponentRegistry {
    Button: { label?: string; disabled?: boolean }
    Card: { title?: string }
  }

  export type ComponentName = keyof ComponentRegistry
  export type ComponentProps<T extends ComponentName> = ComponentRegistry[T]
  export type OptionalPropsComponent = /* components with no required props */
  export type RequiredPropsComponent = Exclude<ComponentName, OptionalPropsComponent>
}

Not reachable in v0.1. componentTypesCodegenPlugin is not re-exported from jafjs/vite, and the subpath its own documentation names does not exist in the package. There is no supported import for it today, so components.d.ts is not generated in any default setup. The generator itself works; only the wiring is missing.

Because component tags are resolved by filename at build time rather than by import, these types are for editor completion only. They do not check your templates - a typo in a prop name inside <Button labl="x" /> is not a TypeScript error.

Types Inside Component Scripts

A component's <script> is compiled by SWC, and TypeScript annotations are accepted and stripped:

source
<script>
interface User {
  id: string
  name: string
}

let users: User[] = []
let selected: User | null = null

function select(user: User) {
  selected = user
}
</script>

These annotations are erased, not checked - tsc does not see inside .html files.

Put logic you want type-checked in a .ts module and import it.

source
<script>
import { formatPrice } from '../lib/format'   // fully type-checked

let total = formatPrice(1299)
</script>

What Needs an Import

Most of what you use in a component is injected and needs no import: effect, computed, storage(), url(), path(), unite(), query(), mutation(), ui and the lifecycle hooks.

The package entry is for the handful that are not:

source
import {
  reactive, effect, computed,   // outside a component
  lazy,
  setErrorHandler, handleError,
  storage, store, persistedStore,
  mountComponent, loadComponent,
} from 'jafjs'

The router is a subpath of its own, and none of it is global: import { navigate, navigateTo, generatePath, guards } from 'jafjs/router'. The API Reference lists every subpath.