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;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[];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.
| Parameter | What it takes |
|---|---|
items | the payload, typed unknown because it usually arrives as parsed JSON |
known | a registry, or a plain list of type names |
required | a 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;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;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.'| Key | Shape | Warned when |
|---|---|---|
UNKNOWN_WIDGET | (type, id) => string | an item’s type is not a key of the registry |
MALFORMED_ITEMS | string | the items prop is not an array |
MALFORMED_ITEM | (id, type) => string | an entry is not an object, or carries no usable type |
MALFORMED_CHILDREN | (id) => string | an 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[];
}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>;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>;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;
}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[];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.