Authoring pipeline
@golemui/dx is the form authoring pipeline with the widget set factored out: the item walker, selector resolver, widget merger, event wiring, and state expansion that power gui.*, packaged so any widget set can build its own namespace on top. It ships as a single public entry point:
npm i @golemui/dx @golemui/coreThe pipeline is four steps: create a registry, register your shortcut types, build a selector chain, and hand all three to createImplementation.
1. Create a registry
Section titled “1. Create a registry”import { createItemTypeRegistry } from '@golemui/dx';
const registry = createItemTypeRegistry();Each widget set owns its own registry instance, so shortcut names are free to overlap across sets. Three rules to know:
- Registering the same item type twice in one registry throws.
- Every type declares a
kind(input,action,display, orlayout). Umbrella selectors likeselectors.inputs(...)match by that kind. - A type registered without a kind never matches an umbrella selector.
2. Declare shortcut types
Section titled “2. Declare shortcut types”A shortcut type maps one authoring call (like myset.inputs.textInput(...)) to an engine widget. createShortcutType builds the definition without registering it, so registration stays an explicit act of the module that owns the registry instead of an import side effect that bundlers may drop:
import { createShortcutType, registerShortcutType } from '@golemui/dx';import type { NonFunctionWidget } from '@golemui/core';
const textFieldType = createShortcutType<TextFieldEntry, TextFieldDecorator>({ itemType: 'MYSET_TEXT_FIELD', kind: 'input', entryShape: 'keyed', mapToWidget: (def) => ({ uid: def.uid ?? '', kind: 'input', type: 'mysetText', path: def.path ?? '', ...(def.label != null ? { label: def.label } : {}), props: {}, }) as NonFunctionWidget,});
registerShortcutType(registry, textFieldType);entryShape tells the walker what the authoring call produces:
| Shape | Used for | Entry looks like |
|---|---|---|
keyed | inputs | { key, def }: the data path plus a decorator |
bare | actions, displays | a decorator or render callback on its own |
compound | layouts | { def, children }: a decorator plus child entries |
Optional hooks cover the harder cases: sensibleDefaults (auto labels and placeholders), afterMerge (post-process the merged widget), buildCustomWidget (full control over the emitted widget), and getChildren (extract children from a compound entry).
defineShortcutType(registry, config) does both steps in one call and returns just the selector factories. Prefer the split form when your types live in separate modules from the registry.
Each shortcut type definition also carries two selector factories, gsl and gslByUid, which become the per-type methods of your selector chain in the next step.
3. Build the selector chain
Section titled “3. Build the selector chain”import { createSelectors } from '@golemui/dx';
const selectors = createSelectors({ textFields: textFieldType.gsl, textFieldByUid: textFieldType.gslByUid,});createSelectors returns the chain root that becomes myset.selectors. On top of your map it auto-generates the eight umbrella methods (inputs, inputByUid, actions, actionByUid, displays, displayByUid, layouts, layoutByUid, matching by registered kind) and the four scope methods (tag, state, tagsAnd, tagsOr). Rules:
- A map entry with the same name as an umbrella method replaces the generated one.
- The scope method names are reserved: using
tag,state,tagsAnd, ortagsOras a map key throws. - Scope methods are immutable: each call returns a new chain level, so a scoped chain can be stored and reused.
4. Create the implementation
Section titled “4. Create the implementation”createImplementation assembles the pieces into the public surface of the widget set:
import { createImplementation, type DxAdapter } from '@golemui/dx';
const adapter: DxAdapter = { bareItemToWidget: (renderFn) => mysetDisplay(renderFn), rootEntry: (children) => mysetVerticalStack(children),};
const facade = { inputs: { textInput: mysetTextInput /* ... */ }, actions: { button: mysetButton /* ... */ }, displays: { display: mysetDisplay /* ... */ }, layouts: { flex: mysetFlex /* ... */ },};
export const implementation = createImplementation< typeof facade, typeof selectors>({ name: 'myset', registry, facade, selectors, adapter,});
export const myset = implementation.namespace;The adapter answers two questions the pipeline cannot answer generically: how a bare render function becomes a display widget, and how top-level definitions get wrapped in a root layout (the root must resolve to the reserved flex type).
The result gives you everything downstream code needs:
| Property | What it is |
|---|---|
namespace | your facade plus selectors: the object you export as myset |
formDefs | the DxService that processes definitions (what gui’s formDefs is) |
resolveFormInput | the function the framework form components consume |
name, registry | echoed back, name is used in error messages |
Two sharp edges: the facade must not contain a selectors key (that slot is filled by the chain, and createImplementation throws if it would be shadowed), and when you pass type arguments you must pass all three together, so name the facade and chain types first as shown above.
Dependencies typing
Section titled “Dependencies typing”If your widget set threads a typed dependency object through forms (like an icon pack), declare its shape once and it flows to formConfig.dependencies and the form components:
type MySetDependencies = { icons?: IconPack };
export const implementation = createImplementation< typeof facade, typeof selectors, MySetDependencies>({ name: 'myset', registry, facade, selectors, adapter });See also
Section titled “See also”- Framework form components: ship the set as a component per framework
- Generating a schema tree: the widget manifest, which mirrors the types you register here
- Widget Sets overview: how the parts fit, and how
guiuses this same pipeline - Selectors: the selector semantics your chain exposes to form authors