Core/Packages/Runtime Core/API reference
@sigx/runtime-core · Stable

API reference#

Exports of @sigx/runtime-core v0.15.3.

All examples assume a named import from the package root:

TypeScript
import { component, defineApp } from '@sigx/runtime-core';

The package also exposes ./jsx-runtime and ./jsx-dev-runtime subpaths (both aliasing the main build) for TSX transpilation via jsxImportSource, a separate ./internals entry for renderer authors, and an ./inspect entry with the inspection-only topic registry for tooling (see Inspection).

Component system#

component#

Define a component from a setup function that returns a render (view) function. setup runs once; the returned function is the reactive render. Setup may be sync or async — async setup is awaited during SSR and errors on the client (SIGX102).

TypeScript
function component<
  TCombined extends Record<string, any> = {},
  TRef = ExtractExposed<TCombined>,
  TSlots = ExtractSlots<TCombined>
>(
  setup: (ctx: ComponentSetupContext<PlatformElement, StripInternalMarkers<TCombined>, TCombined, TRef, TSlots>) => ViewFn | Promise<ViewFn>,
  options?: ComponentOptions
): ComponentFactory<TCombined, TRef, TSlots>;
  • setup — the setup function receiving a ComponentSetupContext; returns a ViewFn (or a promise of one).
  • options — optional ComponentOptions (currently { name? }).
  • Returns a ComponentFactory usable as a JSX element.

getCurrentInstance#

Return the setup context of the currently executing component, or null outside setup. Used to build composables.

TypeScript
function getCurrentInstance(): ComponentSetupContext<any, any, any> | null;
  • Returns the active ComponentSetupContext, or null if called outside a component's setup.

getComponentMeta#

Return the registered metadata (name and setup) for a component factory. Used by DevTools.

TypeScript
function getComponentMeta(factory: Function): { name?: string; setup: SetupFn<any, any, any, any> } | undefined;
  • factory — a component factory.
  • Returns { name?, setup }, or undefined if the factory is not registered.

isComponent#

Type guard that returns true for SignalX components (functions carrying a __setup property), and false for plain function components or strings.

TypeScript
function isComponent(type: unknown): type is ComponentLike;
  • type — the value to test.
  • Returns true if it is a SignalX component factory.

compound#

Create a compound component by attaching sub-components as static properties. Enables the Parent.Child pattern (e.g. Menu.Item, Card.Body) while preserving full TypeScript inference for both the parent and its children.

TypeScript
function compound<
  TMain extends AnyComponentFactory,
  TSub extends Record<string, AnyComponentFactory>
>(main: TMain, sub: TSub): TMain & TSub;
  • main — the main/parent component factory.
  • sub — an object whose values are the sub-components to attach.
  • Returns the main component with the sub-components attached as static properties.

Lifecycle hooks#

Each hook must be called synchronously during setup. Called outside a component they warn and no-op (no throw).

onMounted#

Register a callback to run after the component mounts. Receives a MountContext with the root element.

TypeScript
function onMounted(fn: (ctx: MountContext) => void): void;
  • fn — callback invoked post-mount with { el }.

onUnmounted#

Register a cleanup callback to run when the component unmounts. The place to clear timers and unsubscribe.

TypeScript
function onUnmounted(fn: (ctx: MountContext) => void): void;
  • fn — cleanup callback invoked on unmount with { el }.

onCreated#

Register a callback that runs immediately after setup completes, before the first render.

TypeScript
function onCreated(fn: () => void): void;
  • fn — callback invoked once after setup, before first render.

onUpdated#

Register a callback that runs after every reactive re-render of the component.

TypeScript
function onUpdated(fn: () => void): void;
  • fn — callback invoked after each re-render.

Application#

defineApp#

Create a renderer-agnostic application instance with a chainable use / hook / directive / defineProvide / mount / unmount API.

TypeScript
function defineApp<TContainer = any>(rootComponent: JSXElement): App<TContainer>;
  • rootComponent — the root JSX element to render.
  • Returns an App<TContainer> instance.

JSX runtime#

jsx#

The core JSX factory called by TSX transpilation. Builds VNodes, resolves components, and processes model bindings.

TypeScript
function jsx(type: string | Function | typeof Fragment, props: JSXProps | null, key?: string): JSXElement;
  • type — an intrinsic tag, component factory, or Fragment.
  • props — element props, or null.
  • key — optional reconciliation key.
  • Returns a JSXElement. The fast path skips props cloning unless a model / model:* prop is present.

jsxs#

JSX factory variant for elements with multiple children; delegates to jsx.

TypeScript
function jsxs(type: string | typeof Fragment | Function, props?: Record<string, unknown>, key?: string): JSXElement;
  • type — an intrinsic tag, component factory, or Fragment.
  • props — element props.
  • key — optional reconciliation key.
  • Returns a JSXElement.

jsxDEV#

Dev-mode JSX factory; an alias of jsx.

TypeScript
const jsxDEV: typeof jsx;

Fragment#

VNode type marker for fragments — grouping children without a wrapper element.

TypeScript
const Fragment: unique symbol; // Symbol.for('sigx.Fragment')

Text#

VNode type marker for text nodes.

TypeScript
const Text: unique symbol; // Symbol.for('sigx.Text')

Comment#

VNode type marker for comment / placeholder nodes. Falsy children become Comment placeholders so positional diffing does not shift sibling indices when conditionals toggle.

TypeScript
const Comment: unique symbol; // Symbol.for('sigx.Comment')

Lazy loading & async#

lazy#

Wrap a dynamic-import loader into a lazy component loaded on first render. Adds preload() and isLoaded(). While the chunk loads the wrapper renders null; wrap it in <Defer> for a fallback. A load rejection throws from render (routes to the nearest errorScope, then app.onError).

TypeScript
function lazy<T extends AnyComponentFactory>(loader: ComponentLoader<T>): LazyComponentFactory<T>;
  • loader — a function returning a dynamic import of the component.
  • Returns a LazyComponentFactory<T> (the component plus preload / isLoaded / __lazy).

Defer#

Boundary component that shows a fallback while lazy chunks beneath it load (a pending useData read renders through its own component's match(), not a wrapper). On SSR the fallback streams with the shell and one replacement arrives when everything pending below it — chunks and keyed data — resolves.

TypeScript
const Defer: ComponentFactory<DeferProps, void, { default?: () => JSXElement | JSXElement[] | null }>;
  • Accepts a fallback prop (a node or a function returning a node) and a default slot.

isLazyComponent#

Type guard for lazy-loaded components (checks the __lazy marker).

TypeScript
function isLazyComponent(component: any): component is LazyComponentFactory<any>;
  • component — the value to test.
  • Returns true if it is a lazy component factory.

useData#

The keyed async read. Auto-runs its fetcher and returns a reactive AsyncState<T>; the render re-runs as the state changes. Every read has a key — the reactive trigger, the cache / SSR identity, and the fetcher's input. A static string key is SSR-transferable: it runs during SSR, serializes its value under the key into window.__SIGX_ASYNC__, restores on hydration without refetching, and dedupes per key. A getter key may return a string or a tuple (serialized to canonical JSON); a falsy result holds the read in idle.

TypeScript
// Static key: runs on the server, serialized under `key`, restored on hydration.
function useData<T>(
  key: string,
  fetcher: (arg: string, ctx: AsyncFetcherContext) => Promise<T>,
  opts?: AsyncOptions
): AsyncState<T>;
// Reactive key: string or tuple; a falsy result skips the fetch (state 'idle').
function useData<T, const K extends KeyValue>(
  key: () => K | Falsy,
  fetcher: (arg: K, ctx: AsyncFetcherContext) => Promise<T>,
  opts?: AsyncOptions
): AsyncState<T>;
  • key — a static string, or a getter returning a string / tuple / falsy. A static tuple is rejected (parameters that change belong in a getter).
  • fetcher — an async function producing the value; receives the resolved key and a context whose signal is an AbortSignal. Runs untracked.
  • opts.server — run the fetcher during SSR. Default true; server: false renders the pending arm on the server and fetches after hydration (still keyed).
  • Returns an AsyncState<T> — see AsyncState.

useAction#

The async write. The manual counterpart to useData: never auto-runs, triggered by .run(input). run never rejects — it resolves a settled RunResult<T>. In-flight runs are never aborted; a newer run() or reset() supersedes the observation (the older run resolves { ok: false, error: SupersededError } and never writes state).

TypeScript
function useAction<T, In = void>(
  fn: (input: In, ctx: AsyncFetcherContext) => Promise<T>,
  opts?: ActionOptions
): AsyncAction<T, In>;
  • fn — the write; receives the run input and a context whose signal is an AbortSignal.
  • Returns an AsyncAction<T, In> with state (idle | pending | ready | errored), value, error, loading (state === 'pending'), match(), run(input): Promise<RunResult<T>>, and reset().

all#

Combine several AsyncStates into one all-or-nothing state (one match for a whole view). A pure derived view — no fetching. Object form gives named value/errors records; the rest-tuple form gives positional ones.

TypeScript
function all<S extends Record<string, AsyncState<unknown>>>(sources: S): AllState<ValuesOf<S>, ErrorsOf<S>>;
function all<S extends readonly AsyncState<unknown>[]>(...sources: S): AllState<ValuesOf<S>, ErrorsOf<S>>;

Derived state: any member idleidle; else any errorederrored; else any pendingpending; else any refreshingrefreshing; else ready. AllState<T, E> extends AsyncState<T> with errors: E (collect-all; .error stays first-error-wins).

useStream#

Composable for progressive text (LLM-token-style). Accumulates a streamed AsyncIterable<string> into a reactive string; SSR-aware via the key (streams on the server, restores on hydration without re-running the source, live on client navigation).

TypeScript
function useStream(
  key: string,
  source: () => AsyncIterable<string>
): { readonly value: string };
  • key — the serialization/restore key.
  • source — a function returning an async iterable of text chunks.
  • Returns a { value } string signal that grows as chunks arrive.

Models & two-way binding#

registerModelProcessor#

Register an extension model processor for intrinsic-element model handling. Extension processors run before the platform processor (DOM/Lynx/Terminal), in registration order, and the first one returning true wins — so packs and apps can add model behavior for custom elements or widget libraries without replacing the platform's. Registering the same function twice is a no-op.

TypeScript
function registerModelProcessor(fn: ModelProcessor): void;
  • fn — a ModelProcessor: (type, props, modelBinding, originalProps) => boolean. Return true to claim the binding (skip the fallback), false to defer to later processors.

setPlatformModelProcessor is the separate, platform-identity seam used by renderer packages and is unchanged — registerModelProcessor is the additive extension tier.

createModel#

Create a two-way binding Model<T> from a [sourceObject, key] tuple and an update handler.

TypeScript
function createModel<T>(tuple: [object, string], updateHandler: (value: T) => void): Model<T>;
  • tuple — a [sourceObject, key] pair identifying the bound value.
  • updateHandler — called with the new value on write.
  • Returns a Model<T>.

createModelFromBinding#

Create a Model<T> from a full binding tuple [obj, key, handler] for forwarding scenarios.

TypeScript
function createModelFromBinding<T>(binding: ModelBindingTuple<T>): Model<T>;
  • binding — a [obj, key, handler] tuple.
  • Returns a Model<T>.

isModel#

Type guard that detects Model<T> objects via the sigx.model symbol. Used by the JSX runtime.

TypeScript
function isModel(value: unknown): value is Model<unknown>;
  • value — the value to test.
  • Returns true if it is a Model.

Error handling#

errorScope#

Setup-time error boundary for the calling component's own subtree — a function, not a wrapper component. Catches setup / render / reactive-re-render throws beneath it and data errors bubbled by a match() with no error arm. Does not catch fetcher rejections (those are values on .error) or DOM event-handler throws (those go to app.onError). retry is a real remount.

TypeScript
function errorScope(options: ErrorScopeOptions): void;

interface ErrorScopeOptions {
  fallback?: (error: Error, retry: () => void) => JSXElement;
  onError?: (error: Error, instance: ComponentInstance | null, info: string) => void;
}
  • fallback — rendered in place of the subtree while errored; omitted ⇒ renders nothing (the scope still stops propagation).
  • onError — observer called before the fallback renders; its own throws are swallowed.

SupersededError#

Error a superseded useAction run resolves with ({ ok: false, error: SupersededError }); never written to .error.

TypeScript
class SupersededError extends Error {
  readonly name = 'SupersededError';
}

SigxError#

Base error class for all runtime errors; carries a stable code (SIGX###) and an optional suggestion.

TypeScript
class SigxError extends Error {
  readonly code: string;
  readonly suggestion?: string;
  declare readonly cause?: Error;
  constructor(message: string, options: { code: string; suggestion?: string; cause?: Error });
}
  • code — the stable error code.
  • suggestion — optional remediation hint.
  • cause — optional underlying error.

SigxErrorCode#

Frozen map of error code constants grouped by category (app / render / DI).

TypeScript
const SigxErrorCode: {
  readonly NO_MOUNT_FUNCTION: 'SIGX001';
  readonly RENDER_TARGET_NOT_FOUND: 'SIGX100';
  readonly MOUNT_TARGET_NOT_FOUND: 'SIGX101';
  readonly ASYNC_SETUP_CLIENT: 'SIGX102';
  readonly PROVIDE_OUTSIDE_SETUP: 'SIGX200';
  readonly PROVIDE_INVALID_INJECTABLE: 'SIGX201';
};

noMountFunctionError#

Construct the SIGX001 error — no mount function provided and no platform default.

TypeScript
function noMountFunctionError(): SigxError;
  • Returns the SIGX001 SigxError.

renderTargetNotFoundError#

Construct the SIGX100 error — render target selector not found.

TypeScript
function renderTargetNotFoundError(selector: string): SigxError;
  • selector — the selector that could not be resolved.
  • Returns the SIGX100 SigxError.

mountTargetNotFoundError#

Construct the SIGX101 error — mount target selector not found.

TypeScript
function mountTargetNotFoundError(selector: string): SigxError;
  • selector — the selector that could not be resolved.
  • Returns the SIGX101 SigxError.

asyncSetupClientError#

Construct the SIGX102 error — async setup attempted on the client (only supported during SSR).

TypeScript
function asyncSetupClientError(componentName: string): SigxError;
  • componentName — the offending component's name.
  • Returns the SIGX102 SigxError.

provideOutsideSetupError#

Construct the SIGX200 error — defineProvide called outside component setup.

TypeScript
function provideOutsideSetupError(): SigxError;
  • Returns the SIGX200 SigxError.

provideInvalidInjectableError#

Construct the SIGX201 error — defineProvide called with a non-injectable.

TypeScript
function provideInvalidInjectableError(): SigxError;
  • Returns the SIGX201 SigxError.

Dependency injection#

defineInjectable#

Define an injectable service or value. The returned callable token resolves the nearest provided instance up the component tree, or lazily creates a global singleton if none was provided.

TypeScript
function defineInjectable<T>(factory: () => T): InjectableFunction<T>;
  • factory — produces the value when an instance must be created.
  • Returns an InjectableFunction<T> (callable token).

defineProvide#

Provide a new injectable instance at the current component level, visible to descendants. Must run during setup; otherwise throws SIGX200 / SIGX201.

TypeScript
function defineProvide<T>(useFn: InjectableFunction<T>, factory?: () => T): T;
  • useFn — the injectable token to provide.
  • factory — optional override factory for the provided instance.
  • Returns the provided instance.

useAppContext#

Return the current AppContext from the component tree (provided at root during mount / hydrate / SSR), or null.

TypeScript
function useAppContext(): AppContext | null;
  • Returns the active AppContext, or null.

defineFactory#

Create a factory with managed subscriptions, disposal, and a real instance lifetime. Parameterless factories become injectables; parameterized ones return FactoryFunction creators (still usable with defineProvide).

TypeScript
function defineFactory<R>(
  setup: (ctx: SetupFactoryContext, ...args: unknown[]) => R,
  lifetime: Lifetime,
  typeIdentifier?: guid
): InjectableFunction<R & { dispose: () => void }>;
function defineFactory<R, T1>(setup: (ctx: SetupFactoryContext, p1: T1) => R, lifetime: Lifetime, id?: guid): FactoryFunction<[T1], R & { dispose: () => void }>;
// ...overloads up to 5 parameters
  • setup — receives a SetupFactoryContext and any creator arguments. Must return an object or function (primitives throw).
  • lifetime — a Lifetime string literal; honored since core 0.5.0:
    • 'singleton' — one instance per AppContext, created on first resolution and disposed on app.unmount(). Outside any app context, one instance per JS realm.
    • 'scoped' — the nearest instance provided via defineProvide in the component tree; falls back to the app-context instance when no provider exists.
    • 'transient' — a new instance per call, auto-disposed with the calling component (or manually via dispose()).
  • typeIdentifier — optional guid identity.
  • Returns an InjectableFunction (parameterless) or a FactoryFunction creator (parameterized).

dispose is attached as a non-enumerable property (a setup-returned dispose is delegated to) and is idempotent. Parameterized non-transient factories honor arguments at first creation only — later calls resolve the existing shared instance.

SubscriptionHandler#

Collects unsubscribe callbacks and disposes them together. Used by defineFactory contexts.

TypeScript
class SubscriptionHandler {
  add(unsub: () => void): void;
  unsubscribe(): void;
}
  • add(unsub) — register an unsubscribe callback.
  • unsubscribe() — run and clear all registered callbacks.

Directives#

defineDirective#

Identity function that marks a directive definition (with the sigx.directive symbol) for the use:name prop syntax.

TypeScript
function defineDirective<T = any, El = any>(definition: DirectiveDefinition<T, El>): DirectiveDefinition<T, El>;
  • definition — the directive's lifecycle hooks (created / mounted / updated / unmounted).
  • Returns the same definition, branded.

isDirective#

Type guard for directive definitions created by defineDirective.

TypeScript
function isDirective(value: any): value is DirectiveDefinition;
  • value — the value to test.
  • Returns true if it is a directive definition.

Messaging#

createTopic#

Create an in-memory pub/sub Topic<T>. subscribe() auto-unsubscribes via onUnmounted when called inside a component.

TypeScript
function createTopic<T>(options?: CreateTopicOptions): Topic<T>;

interface CreateTopicOptions {
  /** Tooling metadata; topics WITH a namespace register in the inspection registry. */
  namespace?: string;
  /** Tooling metadata. */
  name?: string;
  /** Called when subscriberCount transitions 0 → 1 (refCount pattern). */
  onActivate?(): void;
  /** Called when subscriberCount transitions back to 0 (last unsubscribe or destroy). */
  onDeactivate?(): void;
}
  • options — metadata plus refCount lifecycle hooks.
  • Returns a Topic<T>.

Behavior contract (Topic v2):

  • publish isolates subscriber errors — a throwing handler is logged via console.error and neither skips later subscribers nor propagates into the publisher. Publishing to a destroyed topic is a no-op.
  • subscribe throws on a destroyed topic — a handler attached after destroy() could never be cleaned up, so it is rejected loudly instead of leaking.
  • onActivate / onDeactivate fire on the 0 → 1 and → 0 subscriber transitions, letting producers pay for work only while observed (e.g. @sigx/store runs its per-key state watchers only while the key's event has subscribers). Hook errors are isolated.
  • Topics created with a namespace register in the inspection registry (see Inspection); destroy() unregisters.

createTopicGroup#

Create a typed group of topics keyed by an event map — mitt-level DX on the Topic primitive. Topics are created lazily per key on first access and are namespaced/registered like any other topic.

TypeScript
function createTopicGroup<EventMap extends Record<string, any>>(options?: { namespace?: string }): {
  topics: { [K in keyof EventMap]: Topic<EventMap[K]> };
  destroy(): void;
};
  • options — optional { namespace? } applied to every created topic.
  • Returns { topics, destroy }; destroy() destroys all created topics. Accessing a key on a destroyed group throws.
TypeScript
const group = createTopicGroup<{ loggedIn: User; loggedOut: void }>({ namespace: 'auth#1.events' });
group.topics.loggedIn.publish(user);       // payload type-checked
group.topics.loggedIn.subscribe(u => {});  // u: User
group.destroy();

toSubscriber#

Wrap a Topic as a subscribe-only view, hiding publish and destroy.

TypeScript
function toSubscriber<T>(topic: Topic<T>): { subscribe: (handler: (data: T) => void) => Subscription };
  • topic — the topic to wrap.
  • Returns an object exposing only subscribe.

Inspection (@sigx/runtime-core/inspect)#

A separate ./inspect package entry exposing the inspection-only topic registry for tooling (devtools, diagnostics). The DX contract: typed application code holds Topic<T> references; strings are tooling metadata. The registry is deliberately Topic<unknown>-typed and realm-global — do not use it as an app-level lookup mechanism; share typed topic references instead. Only topics created with a namespace register; destroy() unregisters.

TypeScript
import { getTopic, listTopics, subscribeTopics, onTopicCreated } from '@sigx/runtime-core/inspect';

Patterns use * wildcards matched over ${namespace}.${name} (e.g. 'todos#1.*', '*.actions.*').

getTopic#

TypeScript
function getTopic(namespace: string, name: string): Topic<unknown> | undefined;

Look up a live registered topic by exact namespace and name.

listTopics#

TypeScript
function listTopics(pattern?: string): Topic<unknown>[];

List live registered topics, optionally filtered by a *-wildcard pattern.

subscribeTopics#

TypeScript
function subscribeTopics(
  pattern: string,
  handler: (data: unknown, meta: { namespace: string; name: string }) => void
): Subscription;

Subscribe to every registered topic matching a pattern — both topics that already exist and topics created later. One Subscription tears everything down. Auto-unsubscribes on component unmount when called inside a setup.

onTopicCreated#

TypeScript
function onTopicCreated(handler: (topic: Topic<unknown>) => void): Subscription;

Register a handler for every topic that registers from now on. Handler errors are isolated. Auto-unsubscribes on component unmount when called inside a setup.

Utilities#

signal#

Re-export of signal() from @sigx/reactivity for convenience. The ComponentSetupContext also exposes it as ctx.signal.

TypeScript
export { signal } from '@sigx/reactivity';

Utils#

Static utility helpers; currently exposes isPromise.

TypeScript
class Utils {
  static isPromise(value: any): boolean;
}
  • isPromise(value) — thenable detection; returns true for promise-like values.

guid#

Generate a v4-style GUID string. guid is also a string type alias of the same name (both exported from models/index).

TypeScript
const guid: () => string;
type guid = string;
  • Returns a new GUID string.

Lifetimes#

Lifetime#

Lifetime options for defineFactory (and consumers like @sigx/store's defineStore). A string-literal union; the lifetime is enforced (see defineFactory for the semantics of each value).

TypeScript
type Lifetime = 'singleton' | 'scoped' | 'transient';

Internal#

__registerComponentPlugin#

Internal HMR hook (registerComponentPlugin re-exported under a __-prefixed name). Not part of the stable public API — listed for completeness only.

TypeScript
function __registerComponentPlugin(plugin: ComponentPlugin): void;
  • plugin — a ComponentPlugin with an optional onDefine hook.

Types#

Define (namespace)#

The preferred, discoverable API for typing component props, events, models, slots, and expose. Combine members with the & intersection.

TypeScript
namespace Define {
  type Prop<TName extends string, TType, Required extends boolean = false> =
    Required extends false ? { [K in TName]?: TType } : { [K in TName]: TType };
  type Event<TName extends string, TDetail = void> = { [K in TName]?: EventDefinition<TDetail> };
  type Model<TNameOrType, TType = void> = /* default: model + update:modelValue; named: model:name + update:name */;
  type Slot<TName extends string, TProps = void> = {
    __slots?: { [K in TName]: TProps extends void
      ? () => JSXElement | JSXElement[] | null
      : (props: TProps) => JSXElement | JSXElement[] | null };
  };
  type Expose<T> = { __exposed?: { __type: T } };
}

ComponentSetupContext#

The context object passed to a component's setup function. renderFn / update() are HMR escape hatches.

TypeScript
interface ComponentSetupContext<TElement = PlatformElement, TProps extends Record<string, any> = {}, TEvents extends Record<string, any> = {}, TRef = any, TSlots = {}> extends SetupContext {
  el: TElement;
  signal: typeof signal;
  props: PropsAccessor<TProps>;
  slots: SlotsObject<TSlots>;
  emit: EmitFn<TEvents>;
  parent: ComponentSetupContext | null;
  onMounted(fn: (ctx: MountContext<TElement>) => void): void;
  onUnmounted(fn: (ctx: MountContext<TElement>) => void): void;
  onCreated(fn: () => void): void;
  onUpdated(fn: () => void): void;
  expose(exposed: TRef): void;
  renderFn: ViewFn | null;
  update(): void;
}

ComponentFactory#

Return type of component(). Callable as JSX, plus internal __-branded props used by the renderer.

TypeScript
type ComponentFactory<TCombined extends Record<string, any>, TRef, TSlots> =
  ((props: StripForJSX<Omit<TCombined, EventNames<TCombined>>>
    & EventHandlers<TCombined>
    & SlotProps<TSlots>
    & SyncProps<TCombined>
    & ExternalModelProps<TCombined>
    & JSX.IntrinsicAttributes
    & ComponentAttributeExtensions
    & { ref?: Ref<TRef>; children?: any }) => JSXElement)
  & { __setup: SetupFn<any, any, any, any>; __name?: string; __islandId?: string; __props: any; __events: any; __ref: TRef; __slots: TSlots };

AnyComponentFactory#

Structural constraint accepting any ComponentFactory (used by lazy / compound) without contravariance issues.

TypeScript
type AnyComponentFactory = {
  (...args: any[]): any;
  __setup: SetupFn<any, any, any, any>;
  __props: any;
  __events: any;
  __ref: any;
  __slots: any;
};

Model#

Two-way binding object: .value reads/writes, .binding forwards.

TypeScript
interface Model<T> {
  value: T;
  readonly binding: ModelBindingTuple<T>;
  readonly [MODEL_SYMBOL]: true;
}
// ModelBindingTuple<T> = readonly [object, string, (value: T) => void]

App#

Chainable app instance returned by defineApp(). The _-prefixed members are @internal for renderers.

TypeScript
interface App<TContainer = any> {
  config: AppConfig;
  use<Options>(plugin: Plugin<Options> | PluginInstallFn<Options>, options?: Options): App<TContainer>;
  defineProvide<T>(useFn: InjectableFunction<T>, factory?: () => T): T;
  hook(hooks: AppLifecycleHooks): App<TContainer>;
  directive(name: string, definition: DirectiveDefinition): App<TContainer>;
  directive(name: string): DirectiveDefinition | undefined;
  mount(container: TContainer, mountFn?: MountFn<TContainer>): App<TContainer>;
  unmount(): void;
  _context: AppContext;
  _isMounted: boolean;
  _container: TContainer | null;
  _rootComponent: JSXElement;
}

App / plugin type cluster#

The supporting types for the app and plugin system (from app-types.ts).

TypeScript
interface AppConfig {
  onError?(err: Error, instance: ComponentInstance | null, info: string): boolean | void; // usually set via app.onError(fn)
  warnHandler?(...args: any[]): void;
  performance?: boolean;
}
interface AppLifecycleHooks {
  onComponentCreated?(instance: ComponentInstance): void;
  onComponentMounted?(instance: ComponentInstance): void;
  onComponentUnmounted?(instance: ComponentInstance): void;
  onComponentUpdated?(instance: ComponentInstance): void;
  onComponentError?(err: unknown, instance: ComponentInstance, info: string): boolean | void;
}
interface AppContext {
  app: App;
  provides: Map<symbol, unknown>;
  config: AppConfig;
  hooks: AppLifecycleHooks[];
  directives: Map<string, DirectiveDefinition>;
}
interface Plugin<Options = any> { name?: string; install(app: App, options?: Options): void; }
type PluginInstallFn<Options = any> = (app: App, options?: Options) => void;
type MountFn<TContainer = any> = (element: JSXElement, container: TContainer, appContext: AppContext) => (() => void) | void;
type UnmountFn<TContainer = any> = (container: TContainer) => void;
interface ComponentInstance { name?: string; ctx: ComponentSetupContext; vnode: VNode; }

VNode / JSXElement / JSXChild / JSXChildren#

Core virtual-node and JSX value types.

TypeScript
type VNode = {
  type: string | typeof Fragment | typeof Text | typeof Comment | Function;
  props: Record<string, any>;
  key: string | number | null;
  children: VNode[];
  dom: any | null;
  text?: string | number;
  parent?: VNode | null;
  cleanup?: () => void;
};
type JSXChild = VNode | string | number | boolean | null | undefined | JSXChild[];
type JSXChildren = JSXChild;
type JSXElement = VNode | string | number | boolean | null;

Component-system helper types#

Helper types from component-types.ts. SetupContext and MountContext are augmentation points.

TypeScript
type ViewFn = () => JSXElement | JSXElement[] | undefined;
type SetupFn<TProps, TEvents, TRef, TSlots> =
  (ctx: ComponentSetupContext<PlatformElement, TProps, TEvents, TRef, TSlots>) => ViewFn | Promise<ViewFn>;
interface ComponentOptions { name?: string; }
type PropsAccessor<TProps> = { readonly [K in keyof TProps]: TProps[K] };
type SlotsObject<TSlots = {}> = { default: () => JSXElement[] } & TSlots;
type EmitFn<TEvents> = <TName extends keyof TEvents>(eventName: TName, ...args: any[]) => void;
interface MountContext<TElement = PlatformElement> { el: TElement; }
interface SetupContext {}
type PropsWithDefaults<TProps, D> = { readonly [K in keyof TProps]-?: K extends keyof D ? NonNullable<TProps[K]> : TProps[K] };
type Ref<T> = { current: T | null } | ((instance: T | null) => void);
type Exposed<T extends { __ref: any }> = T['__ref'];
type ComponentRef<T extends { __ref: any }> = Ref<T['__ref']>;

Platform types#

Module-augmentation hooks: platforms set the element type; packages add global component attributes.

TypeScript
interface PlatformTypes {} // platforms declaration-merge `element: HTMLElement`
type PlatformElement = PlatformTypes extends { element: infer E } ? E : any;
interface ComponentAttributeExtensions {} // augmented by SSR to add 'client:load', etc.

Host namespaces (RendererOptions)#

createRenderer(nodeOps) takes a RendererOptions object — the host operations a platform implements. Three of them are optional and concern namespaces.

Core is namespace-agnostic: it knows nothing about SVG. It threads an opaque boolean flag through the tree walk, caches it on each vnode, and hands it back to createElement and patchProp as the ns parameter. What that flag means is the host's business — in @sigx/runtime-dom it means "in the SVG namespace".

TypeScript
interface RendererOptions<HostNode = any, HostElement = any> {
  createElement(type: string, ns?: boolean, isCustomizedBuiltIn?: string): HostElement;
  patchProp(el: HostElement, key: string, prev: any, next: any, ns?: boolean, appContext?: AppContext | null): void;
  // …

  /** The flag for an element, given its parent's. `undefined` parentNS means the
   *  surrounding context is unknown (a hydrated subtree patched from the top) —
   *  hosts typically fall back to a tag-name heuristic there. */
  getElementNamespace?(tag: string, parentNS: boolean | undefined): boolean;

  /** The flag children inherit, given the element's own. Lets a host RESET the
   *  context at a boundary element (in the DOM, `foreignObject`). */
  getChildNamespace?(tag: string, ns: boolean): boolean;

  /** The flag of an element's CONTAINER, given its tag and cached flag. Used when
   *  replacing a node whose surrounding context is unknown. */
  getContainerNamespace?(tag: string, ns: boolean): boolean;
}

Defaults when a hook is absent:

HookAbsent behaviour
getElementNamespaceEvery element resolves to the default namespace (false)
getChildNamespaceChildren inherit the element's flag unchanged
getContainerNamespaceThe container resolves to the default namespace (false)

For renderer authors: a DOM-like custom renderer must implement these to keep SVG working — core no longer detects SVG tags itself, so omitting them resolves everything to the default namespace. @sigx/runtime-dom's nodeOps is the reference implementation. Renderers with no namespace concept (terminal, lynx) simply omit all three.

The ns parameter of createElement/patchProp was previously named isSVG; positions are unchanged, so only the name differs.

Directive types#

Directive lifecycle hook types. DirectiveDefinitionExtensions is an augmentation point (e.g. SSR getSSRProps).

TypeScript
interface DirectiveBinding<T = any> { value: T; oldValue?: T; }
interface DirectiveDefinitionExtensions<T = any> {}
interface DirectiveDefinition<T = any, El = any> extends DirectiveDefinitionExtensions<T> {
  created?(el: El, binding: DirectiveBinding<T>): void;
  mounted?(el: El, binding: DirectiveBinding<T>): void;
  updated?(el: El, binding: DirectiveBinding<T>): void;
  unmounted?(el: El, binding: DirectiveBinding<T>): void;
}
type ResolvedDirective<T = any, El = any> = [DirectiveDefinition<T, El>, T];

AsyncState#

The reactive result of useData (and, via all, the combined state), plus the match arm table, the shared fetcher shape, and the read options. value is the best data the cell has — kept across a same-key refresh() and across a failed fetch, cleared on key change; loading is state === 'pending' only; refresh() never rejects (failures land on .error).

AsyncState<T> is a discriminated union, so state/hasValue checks narrow value without a cast. Extending it with interface X extends AsyncState<T> no longer compiles — intersect instead (type X<T> = AsyncState<T> & { … }).

TypeScript
type AsyncStateName = 'idle' | 'pending' | 'ready' | 'refreshing' | 'errored';

type AsyncState<T> =
  | AsyncIdle<T> | AsyncPending<T> | AsyncReady<T> | AsyncRefreshing<T> | AsyncErrored<T>;

interface AsyncStateBase<T> {                // the shared methods
  match<R>(arms: MatchArms<T, R>): R | undefined;
  refresh(): Promise<void>;                  // never rejects
}

// Each member narrows state / value / hasValue / error / loading:
interface AsyncReady<T> extends AsyncStateBase<T> {
  readonly state: 'ready';
  readonly value: T;                         // narrowed — no cast
  readonly hasValue: true;
  readonly error: null;
  readonly loading: false;
}
interface AsyncErrored<T> extends AsyncStateBase<T> {
  readonly state: 'errored';
  readonly error: Error;                     // splits on hasValue: a cell that
                                             // once succeeded still holds its value
  readonly loading: false;
}

interface MatchArms<T, R> {
  idle?: () => R;                            // defaults to `pending`
  pending?: () => R;                         // omitted ⇒ renders nothing
  error?: (e: Error, ctx: ErrorArmContext<T>) => R; // omitted ⇒ bubbles to errorScope / app onError
  ready: (v: T) => R;                        // required — the only route to a non-null T
}

type ErrorArmContext<T> =
  & { retry: () => void }
  & ({ value: T; hasValue: true } | { value: null; hasValue: false });

type Fetcher<T, Arg> = (arg: Arg, ctx: AsyncFetcherContext) => Promise<T>;
interface AsyncFetcherContext { signal: AbortSignal; } // pass to fetch(url, { signal })

interface AsyncOptions { server?: boolean; }  // default true; false ⇒ client-only keyed read

AsyncAction / RunResult / AllState#

The useAction handle, its settled result, and the combined all() state.

TypeScript
interface AsyncAction<T, In> {
  readonly state: 'idle' | 'pending' | 'ready' | 'errored';
  readonly value: T | null;
  readonly error: Error | null;
  readonly loading: boolean;                 // state === 'pending'
  match<R>(arms: MatchArms<T, R>): R | undefined;
  run(input: In): Promise<RunResult<T>>;     // never rejects
  reset(): void;
}

type RunResult<T> = { ok: true; value: T } | { ok: false; error: Error };

interface AllState<T, E> extends AsyncState<T> {
  readonly errors: E;                        // collect-all counterpart to first-error-wins `.error`
}

interface ActionOptions {}                    // open interface — empty in core; packs augment it

LazyComponentFactory / DeferProps#

Lazy-loading types.

TypeScript
type LazyComponentFactory<T extends AnyComponentFactory> = T & {
  preload: () => Promise<T>;
  isLoaded: () => boolean;
  __lazy: true;
};
type DeferProps = Define.Prop<'fallback', JSXElement | (() => JSXElement)> & Define.Slot<'default'>;

InjectableFunction / SetupFactoryContext#

Dependency-injection types. InjectableFunction is what defineInjectable returns and what defineProvide / app.defineProvide accept.

TypeScript
interface InjectableFunction<T> {
  (): T;
  _factory: () => T;
  _token: symbol;
}
type FactoryFunction<TArgs extends unknown[], TInstance> =
  ((...args: TArgs) => TInstance) & { _factory: () => TInstance; _token: symbol };
interface SetupFactoryContext {
  onDeactivated(fn: () => void): void;
  subscriptions: SubscriptionHandler;
  overrideDispose(onDispose: (fn: () => void) => void): void;
}

FactoryFunction is what a parameterized defineFactory returns — callable with the setup's params and carrying the provide metadata so it works with defineProvide / app.defineProvide.

Subscription / Topic / ModelProcessor / ComponentPlugin#

Messaging, model-processor, and plugin types. ModelProcessor is the signature of an intrinsic-element model handler — register one with registerModelProcessor. ComponentPlugin is a type-only public export (its setter is internal).

TypeScript
interface Subscription { unsubscribe(): void; }
interface Topic<T> {
  /** Tooling metadata (set via createTopic options). */
  readonly namespace?: string;
  readonly name?: string;
  readonly subscriberCount: number;
  readonly hasSubscribers: boolean;
  readonly disposed: boolean;
  publish(data: T): void;
  subscribe(handler: (data: T) => void): Subscription;
  destroy(): void;
}
type ModelProcessor = (type: string, props: Record<string, any>, modelBinding: [Record<string, any>, string], originalProps: Record<string, any>) => boolean;
type ComponentPlugin = { onDefine?: (name: string | undefined, factory: Function, setup: Function) => void };

ModelBinding / EventDefinition#

TypeScript
type ModelBinding<_T> = [object, string];
type EventDefinition<T> = { __eventDetail: T };

See also#

  • Usage guide — practical patterns and examples.
  • Overview — package summary and installation.