Skip to Content
WidgetAPI Reference

Ahead of the release

npm install @evanion/widget gives you 0.1.0. These pages document main, which has changes that release does not.

API reference

Everything @evanion/widget exports, one heading per symbol. Nothing here assumes you have read the pages before it; Getting started teaches the item shape and What it reports carries the message catalogue, and this page links to them rather than teaching either again.

Both renderers re-export every symbol on this page except warnOnce and resetWarnings, and each pins @evanion/widget exactly, so the names below reach an @evanion/react-widget or @evanion/astro-widget consumer from that package’s own entry point.

defineWidgets

function defineWidgets<R extends WidgetRegistry>(registry: R): R;
Signature — a shape, not a call

Returns the registry it was handed, typed as the literal object passed in.

import { , } from '@evanion/widget'; // Whatever your renderer resolves a type to. The core reads the keys and never // calls a value, so a stand-in is enough to show what the keys do. const = () => null; const = () => null; const = ({ : , : }); const = ( [{ : 'root', : 'listing', : { : 'Root' } }], , ); ; // -> []

The generic parameter is the whole job. Annotating the same object as WidgetRegistry widens its keys to string, which takes away the key union an editor completes on and the union a renderer types a component against. It has no effect on validateItems, whose required map is keyed by plain strings.

validateItems

function validateItems( items: unknown, known: KnownWidgetTypes, required?: Record<string, string[]>, ): WidgetProblem[];
Signature — a shape, not a call

Walks items, recurses into each item’s children, and returns every problem it found. It throws nothing and it short-circuits nothing, so one call reports a whole payload.

ParameterWhat it takes
itemsthe payload, typed unknown because it usually arrives as parsed JSON
knowna registry, or a plain list of type names
requireda widget type mapped to the props that must be present and non-blank
import { } from '@evanion/widget'; const = ( [{ : 'root', : 'listting', : { : 'Root' } }], ['listing', 'shelf'], ); ; // -> [{ index: 0, id: 'root', type: 'listting', message: 'unknown widget type' }]

A type is matched as an own key of known, so an item typed constructor or __proto__ is unknown. Ids are checked for duplicates within one sibling list only. What it reports lists every message and what produces it.

warnOnce

function warnOnce(message: string): void;
Signature — a shape, not a call

Writes message to console.warn the first time it is seen and stays quiet every time after, for the life of the process. It writes nothing at all when NODE_ENV is production, and a bundler folds that comparison and drops the call.

A renderer warns from render, so one stale type in a payload would otherwise log on every re-render, and twice over on a page that renders on the server and then hydrates. Every string in ERROR_MESSAGES carries the offending item’s type and id, which is what makes the text a usable key: each bad item is reported once, and a second bad item is still reported.

This is the seam between the core and a renderer. A consumer has no reason to call it, neither renderer re-exports it, and no example on this page runs it, because an example that proved the second call stayed quiet would be a test rather than something to copy.

resetWarnings

function resetWarnings(): void;
Signature — a shape, not a call

Clears the set of messages warnOnce has already written. A set that lives as long as the process is what a dev server wants and what a test file cannot have, since one case’s warning would silence the next case that produces the same message. Each renderer’s test setup calls this before every test. It carries no example for the reason warnOnce carries none: what an example would show is a second call staying quiet, and that is an assertion rather than a line to copy.

ERROR_MESSAGES

The text a renderer warns with. Held here so that a stale widget type reads the same way whether React or Astro drew the page.

import { } from '@evanion/widget'; const = .('listting', 'root'); ; // -> 'Unknown widget type "listting" for widget ID "root". Skipping render.'
KeyShapeWarned when
UNKNOWN_WIDGET(type, id) => stringan item’s type is not a key of the registry
MALFORMED_ITEMSstringthe items prop is not an array
MALFORMED_ITEM(id, type) => stringan entry is not an object, or carries no usable type
MALFORMED_CHILDREN(id) => stringan item’s children is present and is not an array
UNKNOWN'unknown'the stand-in a message uses for an id or type it lacks

VALIDATION_MESSAGES

The text validateItems reports. Exported so a caller can group, count or translate a report without matching on prose, and so a test asserts against the same strings the library emits. Every key and the payload that produces it is on What it reports.

MISSING_FIELD is (field: string) => string; the other eight are plain strings.

AnyWidgetItem

interface AnyWidgetItem<Type extends string = string, Props = object> { id: string; type: Type; props: Props; meta?: WidgetMeta; children?: AnyWidgetItem[]; }
Signature — a shape, not a call

One item, in the loose form data takes before a registry exists: a CMS payload, a fixture, a network response. This is the whole item model, and every renderer draws exactly this. A renderer that can type its components against a registry offers a checked counterpart, and @evanion/react-widget’s WidgetItem<C> widens to this one.

import { } from '@evanion/widget'; import type { } from '@evanion/widget'; const : [] = [ { : 'brass-birmingham', : 'listing', : { : 'Brass: Birmingham', : 4 }, : { : 2 }, }, { : 'tonight', : 'shelf', : { : 'On the table tonight' }, : [{ : 'root', : 'listing', : { : 'Root' } }], }, ]; const = (, ['listing', 'shelf']); ; // -> []

WidgetRegistry

type WidgetRegistry<T = unknown> = Record<string, T>;
Signature — a shape, not a call

A widget type mapped to whatever the renderer resolves it to. T is the renderer’s component type. @evanion/react-widget instantiates it at a ComponentType, which is what drives its compile-time prop check. @evanion/astro-widget leaves it at the default, because an .astro module’s default export is an AstroComponentFactory carrying no prop types: the props of a .astro file live in its frontmatter Props interface, out of the factory’s reach, so validateItems covers that ground at build time.

WidgetMeta

type WidgetMeta = Record<string, unknown>;
Signature — a shape, not a call

The meta vocabulary of a widget set whose chrome declares none. Any object, so an item carries whatever placement data it likes and a chrome that reads meta narrows it by hand.

WidgetProblem

interface WidgetProblem { index: number; id: string; type: string; message: string; }
Signature — a shape, not a call

One line of the report validateItems returns. index is the item’s position within its own sibling list, and -1 when the payload itself is not a list. id and type are the item’s own, or - when the item carries none a reader could use.

KnownWidgetTypes

type KnownWidgetTypes = WidgetRegistry | readonly string[];
Signature — a shape, not a call

The set of widget types a payload may use. A plain list of names is accepted beside a registry so that a webhook handler or a CI script validates a payload without importing the components it will never draw.

Last updated on