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

API reference#

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

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.

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 SWR last-good (kept across a same-key refresh(), cleared on key change); loading is state === 'pending' only; refresh() never rejects (failures land on .error).

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

interface AsyncState<T> {
  readonly state: AsyncStateName;
  readonly value: T | null;
  readonly error: Error | null;
  readonly loading: boolean;                 // state === 'pending' ONLY
  match<R>(arms: MatchArms<T, R>): R | undefined;
  refresh(): Promise<void>;                  // never rejects
}

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

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.