npm package: @inlay/core
Description: Build element trees for Inlay, a UI component system for the AT Protocol.
Author: Dan Abramov
Latest version: 0.0.13

# @inlay/core

Build element trees for Inlay, a UI component system for the AT Protocol.

## Install

```
npm install @inlay/core
```

## Creating elements

The `$` function creates elements. Pass a string [NSID](https://atproto.com/specs/nsid) as the type:

```ts
import { $ } from "@inlay/core";

const el = $("org.atsui.Stack", { gap: "small" },
  $("org.atsui.Text", {}, "Hello"),
  $("org.atsui.Text", {}, "world")
);
```

You can then turn this tree into JSON with `serializeTree(el)`:

```json
{
  "$": "$",
  "type": "org.atsui.Stack",
  "props": {
    "gap": "small",
    "children": [
      {
        "$": "$",
        "type": "org.atsui.Text",
        "props": { "children": ["Hello"] },
        "key": "0"
      },
      {
        "$": "$",
        "type": "org.atsui.Text",
        "props": { "children": ["world"] },
        "key": "1"
      }
    ]
  }
}
```

If the components you use are [published lexicons](https://atproto.com/specs/lexicon#lexicon-publication-and-resolution), you can use the typed `$` overload:

```ts
import { Stack, Text } from "./generated/org/atsui";

$(Stack, { gap: "small" },
  $(Text, {}, "Hello"),
  $(Text, {}, "world")
);
```

Then their props will be checked.

## JSX

Set `@inlay/core` as the JSX import source in your tsconfig:

```json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@inlay/core"
  }
}
```

Then use Lexicon modules as tags:

```tsx
import { jslex } from "@inlay/core";
import * as atsui from "./generated/org/atsui";
const { Stack, Text } = jslex(atsui);

<Stack gap="medium">
  <Text>Hello world</Text>
</Stack>
```

## HTTP imports

In environments that support URL imports (Val Town, Deno), you can import any published Lexicons like this:

```tsx
/* @jsxImportSource npm:@inlay/core */
import { Stack, Text } from "https://lex.val.run/org.atsui.*.ts";

<Stack gap="medium">
  <Text>Hello world</Text>
</Stack>
```

## Lexicons

Components are defined by Lexicons — JSON schemas that declare props. For example, `org.atsui.Avatar`:

```json
{
  "lexicon": 1,
  "id": "org.atsui.Avatar",
  "defs": {
    "main": {
      "type": "procedure",
      "description": "Circular profile image at a fixed size.",
      "input": {
        "encoding": "application/json",
        "schema": {
          "type": "object",
          "required": ["src"],
          "properties": {
            "src": {
              "type": "unknown",
              "description": "Blob ref for the image."
            },
            "did": {
              "type": "string",
              "format": "did",
              "description": "DID of the blob owner. Used to resolve blob URLs."
            },
            "size": {
              "type": "string",
              "maxLength": 32,
              "description": "Size token.",
              "knownValues": ["xsmall", "small", "medium", "large"],
              "default": "medium"
            }
          }
        }
      },
      "output": {
        "encoding": "application/json",
        "schema": {
          "type": "ref",
          "ref": "at.inlay.defs#response"
        }
      }
    }
  }
}
```

Use `@atproto/lex` to generate TypeScript from Lexicon files. For published lexicons, install from the network:

```bash
npm install @atproto/lex
npx lex install org.atsui.Stack org.atsui.Text
npx lex build --out generated
```

For your own lexicons, put `.json` files in a `lexicons/` directory (e.g. `lexicons/com/example/MyComponent.json`) and point `lex build` at them:

```bash
npx lex build --lexicons lexicons --out generated
```

## Serialization

Convert element trees to/from JSON-safe representations:

```ts
import { serializeTree, deserializeTree } from "@inlay/core";

const json = serializeTree(elementTree);
const restored = deserializeTree(json);
```

## API

| Function | Description |
|----------|-------------|
| `$(type, props?, ...children)` | Create an element from a Lexicon module or NSID string |
| `jslex(namespace)` | Cast Lexicon modules for use as JSX components (no-op at runtime) |
| `isValidElement(value)` | Type guard for `Element` |
| `serializeTree(tree, visitor?)` | Convert element tree to JSON-serializable form |
| `deserializeTree(tree, visitor?)` | Inverse of `serializeTree` |
| `walkTree(tree, mapObject)` | Recursive tree walker for plain objects |
| `resolveBindings(tree, resolve)` | Replace `at.inlay.Binding` elements using a resolver callback |

### Types

- **`Element`** — `{ type: string, key?: string, props?: Record<string, unknown> }`
- **`LexiconComponent`** — Object with a `$nsid` field
- **`Component<M>`** — A Lexicon module usable as a JSX component
- **`ElementVisitor`** — `(element: Element) => unknown`