## Contents

- [Exports](#exports) — the three singletons and the shared `QueryApi` shape
- [Components](#components) — `ComponentFilter`, `ComponentNode`, icons
- [Tokens](#tokens) — `TokenFilter`, `TokenNode`, utilities
- [Guides](#guides) — guide ids and shape
- [Facets](#facets) — `FacetNode` shape and how to reach them
- [Error semantics](#error-semantics) — what throws, what returns `undefined`
- [Runtime guarantees](#runtime-guarantees) — module type, Node version, deps

## Exports

```ts
import { components, tokens, guides } from '@frontify/fondue/sdk';
```

Three singletons. Each has the same query surface; each returns its own node types.

```ts
interface QueryApi<Node, Filter> {
    list(): readonly Node[];
    get(id: string): Node | undefined; // never throws; returns undefined for unknown ids
    has(id: string): boolean;
    where(filter: Filter): readonly Node[]; // AND-combined; array-valued clauses OR within
    readonly size: number;
}
```

Plain arrays returned from `list()` / `where()` / `node.related()` are arrays — they don't carry the query surface. Use native `.filter` / `.find`, or navigate back to a facet to re-query.

## Components

```ts
components: QueryApi<ComponentNode, ComponentFilter> & {
    categories(): readonly ComponentFacetNode[];
    category(name: string): ComponentFacetNode | undefined;
    tags(): readonly ComponentFacetNode[];
    tag(name: string): ComponentFacetNode | undefined;
};
```

### `ComponentFilter`

| Clause     | Type                          | Notes                                                               |
| ---------- | ----------------------------- | ------------------------------------------------------------------- |
| `category` | `string \| readonly string[]` | OR within array                                                     |
| `status`   | `string \| readonly string[]` | OR within array                                                     |
| `tag`      | `string \| readonly string[]` | Matches if component carries any tag in the array                   |
| `text`     | `string`                      | Case-insensitive substring across name, description, category, tags |

All clauses AND-combine.

### `ComponentNode`

```ts
interface ComponentNode {
    // scalar fields
    name: string;
    description: string;
    status: string; // '' for icons
    importStatement: string;
    instructions: string; // hand-written usage notes, often empty
    props: readonly ComponentProp[];
    subComponents: readonly ComponentSubComponent[];
    examples: readonly ComponentExample[];
    typeDefinitions: Readonly<Record<string, string>>;

    // graph edges (methods)
    category(): ComponentFacetNode; // throws on data inconsistency only
    tags(): readonly ComponentFacetNode[];
    related(): readonly ComponentNode[]; // unknown names silently skipped
    toJSON(): ComponentDetails;
}

interface ComponentProp {
    name: string;
    type: string;
    required: boolean;
    defaultValue: string | null;
    description: string;
    deprecated: boolean;
    deprecationMessage: string;
}

interface ComponentExample {
    name: string;
    description: string;
    code: string;
    isCanonical: boolean; // there is at most one canonical example per component
}

interface ComponentSubComponent {
    name: string; // e.g. 'Dialog.Header'
    props: readonly ComponentProp[];
}
```

### Icons

Icons are components with `category: 'icon'`. They have:

- empty `status`, empty `props`, empty `related()`, empty `subComponents`
- a non-empty `importStatement` (e.g. `import { IconAdobeCreativeCloud } from '@frontify/fondue/icons';`)
- tags (e.g. `'arrow'`, `'brand'`)

Filter to icons with `components.where({ category: 'icon' })`. Detect at the node with `node.category().name === 'icon'`.

## Tokens

```ts
tokens: QueryApi<TokenNode, TokenFilter> & {
    categories(): readonly TokenFacetNode[];
    category(name: string): TokenFacetNode | undefined;
    types(): readonly TokenFacetNode[];
    type(name: TokenValueType): TokenFacetNode | undefined;
    utilities: TokenUtilitiesApi;
};

type TokenValueType = 'color' | 'float' | 'shadow' | 'string';
```

### `TokenFilter`

| Clause              | Type                                          | Notes                                               |
| ------------------- | --------------------------------------------- | --------------------------------------------------- |
| `category`          | `string \| readonly string[]`                 | e.g. `'colors'`, `'sizes'`                          |
| `type`              | `TokenValueType \| readonly TokenValueType[]` |                                                     |
| `themeable`         | `boolean`                                     |                                                     |
| `keyPathStartsWith` | `string`                                      | Dot-joined keyPath prefix, e.g. `'colors.charts'`   |
| `text`              | `string`                                      | Case-insensitive against id, tailwindClass, keyPath |

### `TokenNode`

```ts
interface TokenNode {
    id: string; // e.g. 'color-charts-primary-default'
    value: string; // often `var(--token)` or a literal
    cssVariable: string; // 'var(--color-charts-primary-default)'
    tailwindClass: string; // e.g. '*-charts-primary'
    themeable: boolean;
    keyPath: readonly string[]; // ['colors','charts','primary','default']

    category(): TokenFacetNode;
    type(): TokenFacetNode;
    toJSON(): Token;
}
```

### Utilities

```ts
tokens.utilities: QueryApi<TokenUtilityNode, TokenUtilityFilter> & {
    classes(): readonly string[];
};
```

No `categories()` / `types()` on utilities — they're a leaf domain.

```ts
interface TokenUtilityFilter {
    themeable?: boolean;
    keyPathStartsWith?: string; // typography utilities live under 'utilities.text'
    text?: string;
}

interface TokenUtilityNode {
    id: string;
    tailwindClass: string; // e.g. 'tw-body-large-strong'
    themeable: boolean;
    keyPath: readonly string[];
    properties: readonly TokenUtilityProperty[];
}

interface TokenUtilityProperty {
    id: string;
    type: TokenValueType;
    value: string;
    cssVariable: string;
}
```

## Guides

```ts
guides: QueryApi<Guide, GuideFilter>;

interface Guide {
    id: string; // slug from filename, e.g. 'getting-started'
    title: string; // extracted from the first `# Title` line
    content: string; // raw markdown body, includes the leading `# Title`
}

interface GuideFilter {
    text?: string; // case-insensitive against id, title, content
}
```

Known ids include `getting-started`, `contributing`, `upgrading`. The bundled set may grow; call `guides.list()` to see the current corpus.

## Facets

`ComponentFacetNode` and `TokenFacetNode` share this shape:

```ts
interface FacetNode<Node, Filter> {
    name: string;
    list(): readonly Node[];
    get(id: string): Node | undefined;
    has(id: string): boolean;
    where(filter: Filter): readonly Node[];
    size: number;
}
```

Reach a facet via the domain's accessor or via a node's edge:

```ts
components.category('input'); // facet
components.tag('cta'); // facet
components.get('Button')?.category(); // facet from a node
tokens.category('colors')?.where({ themeable: true });
```

## Error semantics

- `get(id)` never throws — returns `undefined` for unknown ids.
- `node.related()` silently skips unknown names.
- `node.category()` throws only on internal data inconsistency (a component referencing a category that doesn't exist) — treat as a bug to report, not a runtime case to handle.
- `where(filter)` with an empty filter returns every node in the domain.

## Runtime guarantees

- Synchronous, zero file I/O.
- Pure ES module; Node 18+.
- No peer dependencies, no React, no DOM.
