# TypeScript types

> Generate types for render from your templates' props.

Kiln can write a TypeScript file that describes every template's [`props`](https://kilnlang.com/docs/tags/props/). With it, your editor completes template names in `render`, checks the data you pass, and knows which blocks `renderBlock` can render.

## Generating types

Pass `generateTypes` your environment and the names of your templates. It returns the contents of a declaration file. Write it somewhere your `tsconfig.json` includes.

```ts
import { writeFile } from "node:fs/promises";
import { createKiln, generateTypes } from "@lucidkiln/core";
import { fileLoader } from "@lucidkiln/core/node";

const loader = fileLoader({ root: "src/templates" });
const kiln = createKiln({ loader });

const types = await generateTypes(kiln, await loader.list());
await writeFile("src/kiln.d.ts", types);
```

- Run it when templates change, for example as part of your build or dev server.
- `generateTypes` doesn't touch the file system, so you can run it anywhere, including on Workers.
- Templates are listed in name order, so the file only changes when a template's props or blocks change.
- The file holds only types, so it adds nothing to your bundle.

The built-in loaders have a `list` method. For your own loader, see [Listing templates](https://kilnlang.com/docs/api/loaders/#listing-templates).

## What it checks

The file exports a `Schema` type. Pass it as the second type argument to `createKiln`. The first argument is your request state, or `undefined` when you have none:

```ts
// pages/post.kiln starts with {% props post: Post, related: Post[] = [] %}
import type { Schema } from "./kiln.js";

const kiln = createKiln<undefined, Schema<{ Post: PostView }>>({
	loader,
	types: { Post: (value) => value instanceof PostView },
});

await kiln.render("pages/post", { post }); // OK
await kiln.render("pages/post", { pots: post }); // Type error: "pots" isn't a prop
await kiln.render("pages/post"); // Type error: "post" is required

await kiln.renderBlock("pages/post", "content", { post }); // OK
await kiln.renderBlock("pages/post", "sidebar", { post }); // Type error: no such block
```

Names are checked the same way with or without `.kiln`. A name that isn't in the file, such as one built at runtime, still works. Kiln can't know which template it is, so it accepts any data.

Each environment has its own schema. Generate a file for each template root and import its `Schema` under a different name, such as `SiteSchema` and `EmailSchema`. They can describe the same template name with different props without affecting each other. A schema applies to `render`, `renderBlock` and `renderStream`.

## How the data type is worked out

- A template with `props` accepts exactly those props. This matches strict mode, which rejects anything else. A [rest prop](https://kilnlang.com/docs/tags/props/#other-variables) lets it accept any other key too, and then it must also pass the props its layouts require, unless it sets them at its top level.
- A prop with a default is optional, and also accepts null.
- A page without `props` accepts any data. If its layout declares required props, the page must pass them, unless it sets them at its top level.
- An untyped prop accepts any value.

Kiln's types become these TypeScript types:

| Kiln | TypeScript |
| --- | --- |
| `null`, `boolean`, `number` | The same |
| `string` | `string \| Markup` |
| `markup` | `Markup` |
| `array` | `readonly unknown[]` |
| `hash` | A plain object or a `Map` |
| `set` | `ReadonlySet<unknown>` |
| `date` | `Date` |
| `object` | `object` |
| `"primary"`, `2` | The same literal |
| `string[]` | `readonly (string \| Markup)[]` |
| `{ name: string, url?: string }` | `{ name: string \| Markup; url?: string \| Markup }` |
| `Post` | The type you declare for `Post` |

## Your own types

A prop type such as `Post` is one your app defines. Give it two things:

- A TypeScript type, by passing a map of your types to the generated `Schema`.
- A check that runs when a template receives the value, in the `types` option.

```ts
import { createKiln } from "@lucidkiln/core";
import type { Schema } from "./kiln.js";

type HostTypes = { Post: PostView };

const kiln = createKiln<undefined, Schema<HostTypes>>({
	loader,
	types: {
		Post: (value) => value instanceof PostView,
	},
});
```

- Type names start with a capital letter, followed by letters, digits or underscores.
- A check gets the value and returns true when it fits. It must not be async.
- When the schema lists a host type, TypeScript requires a check for it that narrows to the right type.
- Checks run in strict mode only, like every other prop check. See [How values are checked](https://kilnlang.com/docs/tags/props/#how-values-are-checked).
- A type with a check but no entry in your type map is `unknown` in the generated file.

For project-wide defaults, you can still add to the exported `KilnTemplates` and `KilnTypes` interfaces through TypeScript module augmentation. Environments without an explicit schema use those defaults. An explicit schema uses only its own template and host-type maps.

Your editor reads `KilnTypes` too, along with `KilnFunctions`, `KilnFilters`, `KilnTests` and `KilnGlobals`, to complete properties and show types in templates. See [Types from TypeScript](https://kilnlang.com/docs/tooling/editors/#types-from-typescript).
