@arrow-js/core

reactive()

Creates observable state or computed values.

Signatures

import type { interface Computed<TValue>Computed, 
type Reactive<T extends ReactiveTarget> = { [P in keyof T]: T[P] extends Computed<infer TValue> ? TValue : T[P] extends ReactiveTarget ? T[P] | Reactive<T[P]> : T[P]; } & {
    $on: <P extends keyof T>(property: P, callback: (value?: T[P], oldValue?: T[P]) => void) => void;
    $off: <P extends keyof T>(property: P, callback: (value?: T[P], oldValue?: T[P]) => void) => void;
}
Reactive
, type ReactiveTarget = Record<PropertyKey, unknown> | unknown[]ReactiveTarget } from '@arrow-js/core'
// Observable state declare function function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive<function (type parameter) T in reactive<T extends ReactiveTarget>(data: T): Reactive<T>T extends type ReactiveTarget = Record<PropertyKey, unknown> | unknown[]ReactiveTarget>(data: T extends ReactiveTargetdata: function (type parameter) T in reactive<T extends ReactiveTarget>(data: T): Reactive<T>T):
type Reactive<T extends ReactiveTarget> = { [P in keyof T]: T[P] extends Computed<infer TValue> ? TValue : T[P] extends ReactiveTarget ? T[P] | Reactive<T[P]> : T[P]; } & {
    $on: <P extends keyof T>(property: P, callback: (value?: T[P], oldValue?: T[P]) => void) => void;
    $off: <P extends keyof T>(property: P, callback: (value?: T[P], oldValue?: T[P]) => void) => void;
}
Reactive
<function (type parameter) T in reactive<T extends ReactiveTarget>(data: T): Reactive<T>T>
// Computed value declare function function reactive<T>(effect: () => T): Computed<T> (+1 overload)reactive<function (type parameter) T in reactive<T>(effect: () => T): Computed<T>T>(effect: () => Teffect: () => function (type parameter) T in reactive<T>(effect: () => T): Computed<T>T): interface Computed<TValue>Computed<function (type parameter) T in reactive<T>(effect: () => T): Computed<T>T>

Observable state

Pass an object or array to get a reactive proxy. Property reads are tracked inside watchers and template expressions. Property writes notify observers.

import { function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive } from '@arrow-js/core'

const 
const data: Reactive<{
    count: number;
    items: string[];
}>
data
=
reactive<{
    count: number;
    items: string[];
}>(data: {
    count: number;
    items: string[];
}): Reactive<{
    count: number;
    items: string[];
}> (+1 overload)
reactive
({ count: numbercount: 0, items: string[]items: [] as string[] })
const data: Reactive<{
    count: number;
    items: string[];
}>
data
.count: numbercount++ // triggers observers
const data: Reactive<{
    count: number;
    items: string[];
}>
data
.items: string[] | Reactive<string[]>items.Array<T>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
('hello') // array mutations trigger parent observers

Computed values

Pass an arrow function to create a computed value. The expression re-evaluates when tracked reads change.

import { function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive } from '@arrow-js/core'

const 
const props: Reactive<{
    count: number;
    multiplier: number;
}>
props
=
reactive<{
    count: number;
    multiplier: number;
}>(data: {
    count: number;
    multiplier: number;
}): Reactive<{
    count: number;
    multiplier: number;
}> (+1 overload)
reactive
({ count: numbercount: 2, multiplier: numbermultiplier: 10 })
const
const data: Reactive<{
    total: Computed<number>;
}>
data
=
reactive<{
    total: Computed<number>;
}>(data: {
    total: Computed<number>;
}): Reactive<{
    total: Computed<number>;
}> (+1 overload)
reactive
({
total: Computed<number>total: reactive<number>(effect: () => number): Computed<number> (+1 overload)reactive(() =>
const props: Reactive<{
    count: number;
    multiplier: number;
}>
props
.count: numbercount *
const props: Reactive<{
    count: number;
    multiplier: number;
}>
props
.multiplier: numbermultiplier)
})
const data: Reactive<{
    total: Computed<number>;
}>
data
.total: numbertotal // 20 — reads like a normal value, auto-updates

Manual subscriptions

Prefer watch() or template expressions over manual subscriptions. Use $on/$off only when you need direct per-property control.

Rules
  • Only objects and arrays can be reactive. Primitives cannot.
  • Nested objects are lazily made reactive on first access.
  • reactive() on an already-reactive object returns the same proxy (idempotent).

watch()

Runs side effects that re-execute when tracked reactive reads change.

Signatures

// Single-effect form
declare function function watch<F extends () => unknown>(effect: F): [returnValue: ReturnType<F>, stop: () => void] (+1 overload)watch<function (type parameter) F in watch<F extends () => unknown>(effect: F): [returnValue: ReturnType<F>, stop: () => void]F extends () => unknown>(
  effect: F extends () => unknowneffect: function (type parameter) F in watch<F extends () => unknown>(effect: F): [returnValue: ReturnType<F>, stop: () => void]F
): [ReturnType<F>returnValue: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType
<function (type parameter) F in watch<F extends () => unknown>(effect: F): [returnValue: ReturnType<F>, stop: () => void]F>, function () => voidstop: () => void]
// Getter + afterEffect form declare function function watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void] (+1 overload)watch<function (type parameter) F in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]F extends () => unknown, function (type parameter) A in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]A extends (arg: ReturnType<F>arg: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType
<function (type parameter) F in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]F>) => unknown>(
effect: F extends () => unknowneffect: function (type parameter) F in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]F, afterEffect: A extends (arg: ReturnType<F>) => unknownafterEffect: function (type parameter) A in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]A ): [ReturnType<A>returnValue: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType
<function (type parameter) A in watch<F extends () => unknown, A extends (arg: ReturnType<F>) => unknown>(effect: F, afterEffect: A): [returnValue: ReturnType<A>, stop: () => void]A>, function () => voidstop: () => void]

Parameters

  • effect — A function that reads reactive properties. Runs immediately on creation. In the single-effect form, this is both the tracker and the side effect.
  • afterEffect (optional) — Receives the return value of effect. Only the effect function tracks dependencies; the afterEffect runs after dependency collection.

Returns

A tuple [returnValue, stop]. Call stop() to unsubscribe from all tracked dependencies.

When a watcher is created inside component(), Arrow also stops it automatically when that component unmounts.

Examples

import { function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive, function watch<F extends (...args: unknown[]) => unknown>(effect: F): [returnValue: ReturnType<F>, stop: () => void] (+1 overload)watch } from '@arrow-js/core'

const 
const data: Reactive<{
    price: number;
    quantity: number;
}>
data
=
reactive<{
    price: number;
    quantity: number;
}>(data: {
    price: number;
    quantity: number;
}): Reactive<{
    price: number;
    quantity: number;
}> (+1 overload)
reactive
({ price: numberprice: 25, quantity: numberquantity: 10 })
// Single-effect: tracks and runs in one function const [, const stop: () => voidstop] = watch<() => void>(effect: () => void): [returnValue: void, stop: () => void] (+1 overload)watch(() => { var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(`Total: ${
const data: Reactive<{
    price: number;
    quantity: number;
}>
data
.price: numberprice *
const data: Reactive<{
    price: number;
    quantity: number;
}>
data
.quantity: numberquantity}`)
}) // Getter + effect: separates tracking from side effect watch<() => number, (total: number) => void>(effect: () => number, afterEffect: (total: number) => void): [returnValue: void, stop: () => void] (+1 overload)watch( () =>
const data: Reactive<{
    price: number;
    quantity: number;
}>
data
.price: numberprice *
const data: Reactive<{
    price: number;
    quantity: number;
}>
data
.quantity: numberquantity,
(total: numbertotal) => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(`Total: ${total: numbertotal}`)
) // Stop watching const stop: () => voidstop()
Rules
  • Dependencies are auto-discovered from reactive reads.
  • Dependencies no longer read on subsequent runs are dropped.

html

Tagged template literal that creates an ArrowTemplate.

Signature

import type { type ArrowExpression = ArrowRenderable | ((...args: unknown[]) => ArrowRenderable) | EventListener | ((evt: InputEvent) => void)ArrowExpression, ArrowTemplate } from '@arrow-js/core'

declare function function html(strings: TemplateStringsArray | string[], ...expSlots: ArrowExpression[]): ArrowTemplatehtml(
  strings: TemplateStringsArray | string[]strings: TemplateStringsArray | string[],
  ...expSlots: ArrowExpression[]expSlots: type ArrowExpression = ArrowRenderable | ((...args: unknown[]) => ArrowRenderable) | EventListener | ((evt: InputEvent) => void)ArrowExpression[]
): ArrowTemplate

Compiler output can call html(strings, ...exprs) directly with a generated string[]. Arrow will route it through the same template caching, pooling, hydration, and cleanup path as a tagged literal.

Mounting

An ArrowTemplate is callable. Pass a parent node to mount into the DOM, or call with no arguments to get a DocumentFragment.

const const template: ArrowTemplatetemplate = const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<h1>Hello</h1>`

// Mount to a DOM node
const template: ArrowTemplate
(parent: ParentNode) => ParentNode (+1 overload)
template
(var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
Returns the first element within node's descendants whose ID is elementId. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementById)
getElementById
('app'))
// Get a DocumentFragment const const fragment: DocumentFragmentfragment =
const template: ArrowTemplate
() => DocumentFragment (+1 overload)
template
()

Expression types

  • Static — Any non-function value. Renders once.
    const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<p>${const someString: stringsomeString}</p>`
  • Reactive — A function expression. Re-evaluates when tracked reads change.
    const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<p>${() => 
    const data: {
        active: boolean;
        boundaries: string[];
        count: number;
        list: string[];
        loading: boolean;
        logTotal: boolean;
        price: number;
        quantity: number;
        text: string;
        user: {
            last: string;
        };
    }
    data
    .count: numbercount}</p>`
  • Template / component — Nest directly.
    const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<div>${const otherTemplate: ArrowTemplateotherTemplate}</div>`
    const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<div>${
    function MyComponent(props: {
        label: string;
    }): unknown
    MyComponent
    ({ label: stringlabel: 'hi' })}</div>`
  • Array — Renders a list of templates.
    const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<ul>${() => 
    const items: {
        id: string | number;
        label: string;
        name: string;
    }[]
    items
    .
    Array<{ id: string | number; label: string; name: string; }>.map<ArrowTemplate>(callbackfn: (value: {
        id: string | number;
        label: string;
        name: string;
    }, index: number, array: {
        id: string | number;
        label: string;
        name: string;
    }[]) => ArrowTemplate, thisArg?: any): ArrowTemplate[]
    Calls a defined callback function on each element of an array, and returns an array that contains the results.
    @paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
    map
    (
    i: {
        id: string | number;
        label: string;
        name: string;
    }
    i
    => const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<li>${
    i: {
        id: string | number;
        label: string;
        name: string;
    }
    i
    .name: stringname}</li>`)}</ul>`

Attribute binding

Static or reactive. Return false to remove the attribute.

// Static
const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<div class="${const cls: stringcls}"></div>`

// Reactive
const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<div class="${() => 
const data: {
    active: boolean;
    boundaries: string[];
    count: number;
    list: string[];
    loading: boolean;
    logTotal: boolean;
    price: number;
    quantity: number;
    text: string;
    user: {
        last: string;
    };
}
data
.active: booleanactive ? 'on' : 'off'}"></div>`
// Boolean removal const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<button disabled="${() =>
const data: {
    active: boolean;
    boundaries: string[];
    count: number;
    list: string[];
    loading: boolean;
    logTotal: boolean;
    price: number;
    quantity: number;
    text: string;
    user: {
        last: string;
    };
}
data
.loading: booleanloading ? '' : false}">Submit</button>`

Property binding

Prefix with . to set an IDL property instead of an attribute.

const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<input .value="${() => 
const data: {
    active: boolean;
    boundaries: string[];
    count: number;
    list: string[];
    loading: boolean;
    logTotal: boolean;
    price: number;
    quantity: number;
    text: string;
    user: {
        last: string;
    };
}
data
.text: stringtext}" />`

Event binding

Prefix with @ to attach an event listener.

const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<button @click="${(e: anye) => function handleClick(event: Event): voidhandleClick(e: anye)}">Click</button>`

List keys

Call .key() on a template to give it stable identity in a list. Without keys, list patches reuse slots by position.

const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<ul>${() => 
const items: {
    id: string | number;
    label: string;
    name: string;
}[]
items
.
Array<{ id: string | number; label: string; name: string; }>.map<ArrowTemplate>(callbackfn: (value: {
    id: string | number;
    label: string;
    name: string;
}, index: number, array: {
    id: string | number;
    label: string;
    name: string;
}[]) => ArrowTemplate, thisArg?: any): ArrowTemplate[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(
item: {
    id: string | number;
    label: string;
    name: string;
}
item
=>
const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`<li>${
item: {
    id: string | number;
    label: string;
    name: string;
}
item
.name: stringname}</li>`.ArrowTemplate.key: (key: string | number | undefined) => ArrowTemplatekey(
item: {
    id: string | number;
    label: string;
    name: string;
}
item
.id: string | numberid)
)}</ul>`

component()

Wraps a factory function to provide stable local state across parent re-renders.

Signatures

import type {
  ArrowTemplate,
  AsyncComponentOptions,
  Component,
  ComponentWithProps,
  Props,
  ReactiveTarget,
} from '@arrow-js/core'

// Sync — no props
declare function component(
  factory: () => ArrowTemplate
): Component

// Sync — with props
declare function component<T extends ReactiveTarget>(
  factory: (props: Props<T>) => ArrowTemplate
): ComponentWithProps<T>

// Async — no props
declare function component<TValue, TSnapshot = TValue>(
  factory: () => Promise<TValue> | TValue,
  options?: AsyncComponentOptions<ReactiveTarget, TValue, TSnapshot>
): Component

// Async — with props
declare function component<T extends ReactiveTarget, TValue, TSnapshot = TValue>(
  factory: (props: Props<T>) => Promise<TValue> | TValue,
  options?: AsyncComponentOptions<T, TValue, TSnapshot>
): ComponentWithProps<T>

AsyncComponentOptions

import type { interface AsyncComponentOptions<T extends ReactiveTarget, TValue, TEvents extends EventMap = EventMap, TSnapshot = TValue>AsyncComponentOptions } from '@arrow-js/core'

Usage

import { function component(factory: () => ArrowTemplate): Component (+7 overloads)component, function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml, function onCleanup(fn: () => void): () => voidonCleanup, function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive } from '@arrow-js/core'
import type { type Props<T extends ReactiveTarget> = { [P in keyof T]: T[P] extends ReactiveTarget ? T[P] | Props<T[P]> : T[P]; }Props } from '@arrow-js/core'

// Sync component with props
const const Counter: Component<EventMap>Counter = component<EventMap>(factory: (props: undefined, emit: Emit<EventMap>) => ArrowTemplate): Component<EventMap> (+7 overloads)component((
props: Props<{
    count: number;
}>
props
: type Props<T extends ReactiveTarget> = { [P in keyof T]: T[P] extends ReactiveTarget ? T[P] | Props<T[P]> : T[P]; }Props<{ count: numbercount: number }>) => {
const
const local: Reactive<{
    clicks: number;
}>
local
=
reactive<{
    clicks: number;
}>(data: {
    clicks: number;
}): Reactive<{
    clicks: number;
}> (+1 overload)
reactive
({ clicks: numberclicks: 0 })
const const onResize: () => voidonResize = () => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
('resize')
var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.addEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
('resize', const onResize: () => voidonResize)
function onCleanup(fn: () => void): () => voidonCleanup(() => var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.removeEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | EventListenerOptions): void (+1 overload)
The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
removeEventListener
('resize', const onResize: () => voidonResize))
return function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml`<button @click="${() =>
const local: Reactive<{
    clicks: number;
}>
local
.clicks: numberclicks++}">
Root ${() =>
props: Props<{
    count: number;
}>
props
.count: numbercount} | Local ${() =>
const local: Reactive<{
    clicks: number;
}>
local
.clicks: numberclicks}
</button>` }) // Async component const const UserName: Component<EventMap>UserName = component<any, EventMap, any>(factory: (props: undefined, emit: Emit<EventMap>) => any, options?: AsyncComponentOptions<ReactiveTarget, any, EventMap, any>): Component<EventMap> (+7 overloads)component(async ({ id: stringid }: { id: stringid: string }) => { const const user: anyuser = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(`/api/users/${id: stringid}`).Promise<Response>.then<any, never>(onfulfilled?: (value: Response) => any, onrejected?: (reason: any) => PromiseLike<never>): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
(r: Responser => r: Responser.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
())
return const user: anyuser.name })

.key() for lists

Call .key() on the component call to preserve identity when rendering in a keyed list.

const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`${() => 
const items: {
    id: string | number;
    label: string;
    name: string;
}[]
items
.
Array<{ id: string | number; label: string; name: string; }>.map<unknown>(callbackfn: (value: {
    id: string | number;
    label: string;
    name: string;
}, index: number, array: {
    id: string | number;
    label: string;
    name: string;
}[]) => unknown, thisArg?: any): unknown[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(
item: {
    id: string | number;
    label: string;
    name: string;
}
item
=>
function ItemCard(props: {
    id: string | number;
    name: string;
}): {
    key(key: string | number | undefined): unknown;
}
ItemCard
(
item: {
    id: string | number;
    label: string;
    name: string;
}
item
).function key(key: string | number | undefined): unknownkey(
item: {
    id: string | number;
    label: string;
    name: string;
}
item
.id: string | numberid)
)}`
Rules
  • The factory runs once per slot, not on every update.
  • Never destructure props at the top of the factory — read them lazily inside reactive expressions.
  • Props<T> is a live proxy over the source object, so checks like 'count' in props and Object.keys(props) reflect the current source keys.
  • SSR waits for all async components to resolve before returning HTML.
  • JSON-safe async results are auto-serialized into the hydration payload.

onCleanup()

Registers teardown work for the current component instance.

Signature

declare function function onCleanup(fn: () => void): () => voidonCleanup(fn: () => voidfn: () => void): () => void

Behavior

  • Call it inside component() while setting up local side effects.
  • Arrow runs the cleanup automatically when that component slot unmounts.
  • It also returns a disposer so you can stop the side effect early.

Example

import { function component(factory: () => ArrowTemplate): Component (+7 overloads)component, function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml, function onCleanup(fn: () => void): () => voidonCleanup } from '@arrow-js/core'

const const ResizeProbe: Component<EventMap>ResizeProbe = function component(factory: () => ArrowTemplate): Component (+7 overloads)component(() => {
  const const onResize: () => voidonResize = () => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.innerWidth: number
The read-only Window property **`innerWidth`** returns the interior width of the window in pixels (that is, the width of the window's layout viewport). [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/innerWidth)
innerWidth
)
var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.addEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
('resize', const onResize: () => voidonResize)
function onCleanup(fn: () => void): () => voidonCleanup(() => var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.removeEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | EventListenerOptions): void (+1 overload)
The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
removeEventListener
('resize', const onResize: () => voidonResize))
return function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml`<div>Watching resize…</div>` })
Tip

Use onCleanup() for manual subscriptions like DOM listeners, timers, sockets, or anything else Arrow did not create for you.

pick() / props()

Narrows a reactive object down to specific keys. props is an alias for pick.

Signatures

declare function function pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K> (+1 overload)pick<function (type parameter) T in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>T extends object, function (type parameter) K in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>K extends keyof function (type parameter) T in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>T>(
  source: T extends objectsource: function (type parameter) T in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>T,
  ...keys: K[]keys: function (type parameter) K in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>K[]
): type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
From T, pick a set of properties whose keys are in the union K
Pick
<function (type parameter) T in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>T, function (type parameter) K in pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>K>
declare function function pick<T extends object>(source: T): T (+1 overload)pick<function (type parameter) T in pick<T extends object>(source: T): TT extends object>(source: T extends objectsource: function (type parameter) T in pick<T extends object>(source: T): TT): function (type parameter) T in pick<T extends object>(source: T): TT const
const props: {
    <T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K>;
    <T extends object>(source: T): T;
}
props
= function pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K> (+1 overload)pick // alias

Usage

import { function pick<T extends object, K extends keyof T>(source: T, ...keys: K[]): Pick<T, K> (+1 overload)pick, function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive } from '@arrow-js/core'

const 
const state: Reactive<{
    count: number;
    theme: string;
    locale: string;
}>
state
=
reactive<{
    count: number;
    theme: string;
    locale: string;
}>(data: {
    count: number;
    theme: string;
    locale: string;
}): Reactive<{
    count: number;
    theme: string;
    locale: string;
}> (+1 overload)
reactive
({ count: numbercount: 1, theme: stringtheme: 'dark', locale: stringlocale: 'en' })
// Pass only the keys a component needs const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`${
function Counter(props: {
    count: number;
}): unknown
Counter
(
pick<Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, "count">(source: Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, ...keys: "count"[]): Pick<Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, "count"> (+1 overload)
pick
(
const state: Reactive<{
    count: number;
    theme: string;
    locale: string;
}>
state
, 'count'))}`
// Without keys — returns the source as-is const html: (strings: TemplateStringsArray | string[], ...expressions: unknown[]) => ArrowTemplatehtml`${
function Counter(props: {
    count: number;
}): unknown
Counter
(
pick<Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, "count">(source: Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, ...keys: "count"[]): Pick<Reactive<{
    count: number;
    theme: string;
    locale: string;
}>, "count"> (+1 overload)
pick
(
const state: Reactive<{
    count: number;
    theme: string;
    locale: string;
}>
state
))}`
Tip

The returned object is a live proxy — reads and writes flow through to the source. It is not a copy.

nextTick()

Flushes Arrow's internal microtask queue, then runs an optional callback.

Signature

declare function function nextTick(fn?: CallableFunction): Promise<unknown>nextTick(fn: CallableFunctionfn?: CallableFunction): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<unknown>

Usage

import { function nextTick(fn?: CallableFunction): Promise<unknown>nextTick, function reactive<T extends ReactiveTarget>(data: T): Reactive<T> (+1 overload)reactive } from '@arrow-js/core'

const 
const data: Reactive<{
    count: number;
}>
data
=
reactive<{
    count: number;
}>(data: {
    count: number;
}): Reactive<{
    count: number;
}> (+1 overload)
reactive
({ count: numbercount: 0 })
const data: Reactive<{
    count: number;
}>
data
.count: numbercount = 5
// Wait for all pending reactive updates to flush await function nextTick(fn?: CallableFunction): Promise<unknown>nextTick() // DOM is now updated // Or pass a callback function nextTick(fn?: CallableFunction): Promise<unknown>nextTick(() => { var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
('DOM updated')
})

Arrow batches reactive updates into a microtask. nextTick lets you wait for that flush before reading the DOM or performing follow-up work.

@arrow-js/framework

render()

Full-lifecycle render that mounts a view into a root element, tracking async components and boundaries.

Signature

import type { RenderOptions, RenderResult } from '@arrow-js/framework'

declare function function render(root: ParentNode, view: unknown, options?: RenderOptions): Promise<RenderResult>render(
  root: ParentNoderoot: ParentNode,
  view: unknownview: unknown,
  options: RenderOptionsoptions?: RenderOptions
): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<RenderResult>

RenderOptions

import type { RenderOptions } from '@arrow-js/framework'

RenderResult

import type { RenderPayload, RenderResult } from '@arrow-js/framework'

Usage

import { function render(root: ParentNode, view: unknown, options?: RenderOptions): Promise<RenderResult>render } from '@arrow-js/framework'
import { function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml } from '@arrow-js/core'

const const view: ArrowTemplateview = function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml`<h1>Hello</h1>`
const { const root: ParentNoderoot, const payload: RenderPayloadpayload } = await function render(root: ParentNode, view: unknown, options?: RenderOptions): Promise<RenderResult>render(var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
Returns the first element within node's descendants whose ID is elementId. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementById)
getElementById
('app'), const view: ArrowTemplateview)

boundary()

Wraps a view in hydration boundary markers, enabling targeted recovery during hydration.

Signature

import type { ArrowTemplate } from '@arrow-js/core'
import type { BoundaryOptions } from '@arrow-js/framework'

declare function function boundary(view: unknown, options?: BoundaryOptions): ArrowTemplateboundary(
  view: unknownview: unknown,
  options: BoundaryOptionsoptions?: BoundaryOptions
): ArrowTemplate

Usage

import { function boundary(view: unknown, options?: BoundaryOptions): ArrowTemplateboundary } from '@arrow-js/framework'
import { function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml } from '@arrow-js/core'

function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml`
  <main>
    ${function boundary(view: unknown, options?: BoundaryOptions): ArrowTemplateboundary(function Sidebar(): unknownSidebar(), { BoundaryOptions.idPrefix?: stringidPrefix: 'sidebar' })}
    ${function boundary(view: unknown, options?: BoundaryOptions): ArrowTemplateboundary(function Content(): unknownContent(), { BoundaryOptions.idPrefix?: stringidPrefix: 'content' })}
  </main>
`

This inserts <template data-arrow-boundary-start/end> markers in the HTML. During hydration, if a subtree mismatches, Arrow repairs that boundary region instead of replacing the entire root.

When to use

Always wrap async components in a boundary for SSR/hydration recovery. Also useful around any subtree that may diverge between server and client (e.g. time-dependent content).

toTemplate()

Normalizes any view value into an ArrowTemplate. Useful when you have a value that might be a string, number, template, or component call and need a consistent template type.

Signature

import type { ArrowTemplate } from '@arrow-js/core'

declare function function toTemplate(view: unknown): ArrowTemplatetoTemplate(view: unknownview: unknown): ArrowTemplate

Usage

import { function toTemplate(view: unknown): ArrowTemplatetoTemplate } from '@arrow-js/framework'
import { function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml } from '@arrow-js/core'

const const view: "Hello, world"view = 'Hello, world'
const const template: ArrowTemplatetemplate = function toTemplate(view: unknown): ArrowTemplatetoTemplate(const view: "Hello, world"view)

// Now usable anywhere an ArrowTemplate is expected
const template: ArrowTemplate
(parent: ParentNode) => ParentNode (+1 overload)
template
(var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
Returns the first element within node's descendants whose ID is elementId. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementById)
getElementById
('app'))

renderDocument()

Injects rendered HTML, head content, and payload script into an HTML shell template string. Used in custom server setups.

Signature

import type { DocumentRenderParts } from '@arrow-js/framework'

declare function function renderDocument(template: string, parts: DocumentRenderParts): stringrenderDocument(
  template: stringtemplate: string,
  parts: DocumentRenderPartsparts: DocumentRenderParts
): string

Placeholder markers

The template string should contain these HTML comment placeholders:

  • <!--app-head--> — replaced with parts.head
  • <!--app-html--> — replaced with parts.html
  • <!--app-payload--> — replaced with parts.payloadScript

@arrow-js/ssr

renderToString()

Renders a view to an HTML string on the server. Waits for all async components to resolve before returning.

This is the main SSR entry point. Use it inside your request handler after you have chosen the page and built the Arrow view for the incoming URL.

Signature

import type {
  HydrationPayload,
  SsrRenderOptions,
  SsrRenderResult,
} from '@arrow-js/ssr'

declare function function renderToString(view: unknown, options?: SsrRenderOptions): Promise<SsrRenderResult>renderToString(
  view: unknownview: unknown,
  options: SsrRenderOptionsoptions?: SsrRenderOptions
): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<SsrRenderResult>

Usage

import { function renderToString(view: unknown, options?: SsrRenderOptions): Promise<SsrRenderResult>renderToString, function serializePayload(payload: unknown, id?: string): stringserializePayload } from '@arrow-js/ssr'

const { const html: stringhtml, const payload: HydrationPayloadpayload } = await function renderToString(view: unknown, options?: SsrRenderOptions): Promise<SsrRenderResult>renderToString(const view: unknownview)

// Serialize payload for client-side hydration
const const script: stringscript = function serializePayload(payload: unknown, id?: string): stringserializePayload(const payload: HydrationPayloadpayload)

Typical server flow

import { renderToString, serializePayload } from '@arrow-js/ssr'

export async function renderPage(url: string) {
  const page = routeToPage(url)
  const result = await renderToString(page.view)

  return [
    '<!doctype html>',
    '<html>',
    '  <head>',
    `    <title>${page.title}</title>`,
    '  </head>',
    '  <body>',
    `    <div id="app">${result.html}</div>`,
    `    ${serializePayload(result.payload)}`,
    '  </body>',
    '</html>'
  ].join('\n')
}

Internally uses JSDOM to render templates into a virtual DOM, then serializes the result. All async components are awaited and their results captured in the payload.

serializePayload()

Serializes a hydration payload into a <script type="application/json"> tag that can be embedded in the HTML document.

Signature

declare function function serializePayload(payload: unknown, id?: string): stringserializePayload(
  payload: unknownpayload: unknown,
  id: stringid?: string  // default: 'arrow-ssr-payload'
): string

Returns

An HTML string containing a <script> tag with the JSON-serialized payload. The id attribute matches what readPayload() looks for on the client.

const 
const payload: {
    rootId: string;
    async: {};
    boundaries: any[];
}
payload
= { rootId: stringrootId: 'app', async: {}async: {}, boundaries: undefined[]boundaries: [] }
const const defaultScript: stringdefaultScript = const serializePayload: (payload: unknown, id?: string) => stringserializePayload(
const payload: {
    rootId: string;
    async: {};
    boundaries: any[];
}
payload
)
// <script id="arrow-ssr-payload" type="application/json">{...}</script> // Custom id const const customScript: stringcustomScript = const serializePayload: (payload: unknown, id?: string) => stringserializePayload(
const payload: {
    rootId: string;
    async: {};
    boundaries: any[];
}
payload
, 'my-payload')
// <script id="my-payload" type="application/json">{...}</script>
Tip

The serializer escapes </script> sequences inside the JSON to prevent injection.

@arrow-js/hydrate

hydrate()

Reconciles server-rendered HTML with the client-side view tree, reconnecting reactivity without replacing existing DOM nodes.

Call it once in your browser entry after reading the server payload and rebuilding the same page view that was used during SSR.

Signature

import type {
  HydrationOptions,
  HydrationPayload,
  HydrationResult,
} from '@arrow-js/hydrate'

declare function function hydrate(root: ParentNode, view: unknown, payload?: HydrationPayload, options?: HydrationOptions): Promise<HydrationResult>hydrate(
  root: ParentNoderoot: ParentNode,
  view: unknownview: unknown,
  payload: HydrationPayloadpayload?: HydrationPayload,
  options: HydrationOptionsoptions?: HydrationOptions
): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<HydrationResult>

HydrationOptions

import type {
  HydrationMismatchDetails,
  HydrationOptions,
} from '@arrow-js/hydrate'

HydrationResult

import type { HydrationResult } from '@arrow-js/hydrate'

Usage

import { function hydrate(root: ParentNode, view: unknown, payload?: HydrationPayload, options?: HydrationOptions): Promise<HydrationResult>hydrate, function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload } from '@arrow-js/hydrate'
import { function createApp(): ArrowTemplatecreateApp } from './app'

const const payload: HydrationPayloadpayload = function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload()
const const root: HTMLElementroot = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
Returns the first element within node's descendants whose ID is elementId. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementById)
getElementById
('app')!
const const result: HydrationResultresult = await function hydrate(root: ParentNode, view: unknown, payload?: HydrationPayload, options?: HydrationOptions): Promise<HydrationResult>hydrate(const root: HTMLElementroot, function createApp(): ArrowTemplatecreateApp(), const payload: HydrationPayloadpayload, { HydrationOptions.onMismatch?: (details: HydrationMismatchDetails) => voidonMismatch: (details: HydrationMismatchDetailsdetails) => { var console: Consoleconsole.Console.warn(...data: any[]): void
The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)
warn
('Hydration mismatch:', details: HydrationMismatchDetailsdetails)
} })

Typical client flow

import { hydrate, readPayload } from '@arrow-js/hydrate'

const payload = readPayload()
const root = document.getElementById(payload.rootId ?? 'app')

if (!root) {
  throw new Error('Missing #app root')
}

await hydrate(
  root,
  routeToPage(window.location.pathname).view,
  payload
)

When the server HTML matches the client view, Arrow adopts the existing DOM nodes and attaches reactive bindings. When a mismatch is detected, boundary regions are repaired individually before falling back to a full root replacement.

readPayload()

Reads the hydration payload from a <script type="application/json"> tag embedded in the document by the server.

Signature

declare function function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload(
  doc: Documentdoc?: Document,  // default: document
  id: stringid?: string      // default: 'arrow-ssr-payload'
): type HydrationPayload = HydrationPayloadHydrationPayload

Usage

import { function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload } from '@arrow-js/hydrate'

// Default — reads from document, id="arrow-ssr-payload"
const const payload: HydrationPayloadpayload = function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload()

// Custom document and id
const const iframe: HTMLIFrameElementiframe = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.ParentNode.querySelector<"iframe">(selectors: "iframe"): HTMLIFrameElement (+4 overloads)
Returns the first element that is a descendant of node that matches selectors. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector)
querySelector
('iframe')
const const payloadFromFrame: HydrationPayloadpayloadFromFrame = const iframe: HTMLIFrameElementiframe?.HTMLIFrameElement.contentDocument: Document
If the iframe and the iframe's parent document are Same Origin, returns a `Document` (that is, the active document in the inline frame's nested browsing context), else returns `null`. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement/contentDocument)
contentDocument
? function readPayload(doc?: Document, id?: string): HydrationPayloadreadPayload(const iframe: HTMLIFrameElementiframe.HTMLIFrameElement.contentDocument: Document
If the iframe and the iframe's parent document are Same Origin, returns a `Document` (that is, the active document in the inline frame's nested browsing context), else returns `null`. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement/contentDocument)
contentDocument
, 'my-payload')
: null

@arrow-js/sandbox

sandbox()

Returns an ArrowTemplate that renders a stable <arrow-sandbox> host element and boots a QuickJS + WASM VM behind it.

Signature

import type { ArrowTemplate } from '@arrow-js/core'

type type HostBridgeFn = (...args: unknown[]) => unknown | Promise<unknown>HostBridgeFn = (...args: unknown[]args: unknown[]) => unknown | interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<unknown>
type
type HostBridgeModule = {
    [x: string]: HostBridgeFn;
}
HostBridgeModule
= type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, type HostBridgeFn = (...args: unknown[]) => unknown | Promise<unknown>HostBridgeFn>
type
type HostBridge = {
    [x: string]: HostBridgeModule;
}
HostBridge
= type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string,
type HostBridgeModule = {
    [x: string]: HostBridgeFn;
}
HostBridgeModule
>
interface SandboxProps { SandboxProps.source: Record<string, string>source: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, string>
SandboxProps.shadowDOM?: booleanshadowDOM?: boolean; SandboxProps.onError?: (error: Error | string) => voidonError?: (error: string | Errorerror: Error | string) => void; SandboxProps.debug?: booleandebug?: boolean; } interface SandboxEvents { SandboxEvents.output?: (payload: unknown) => voidoutput?: (payload: unknownpayload: unknown) => void; } declare function
function sandbox<T extends {
    source: object;
    shadowDOM?: boolean;
    onError?: (error: Error | string) => void;
    debug?: boolean;
}>(props: T, events?: SandboxEvents, hostBridge?: HostBridge): ArrowTemplate
sandbox
<
function (type parameter) T in sandbox<T extends {
    source: object;
    shadowDOM?: boolean;
    onError?: (error: Error | string) => void;
    debug?: boolean;
}>(props: T, events?: SandboxEvents, hostBridge?: HostBridge): ArrowTemplate
T
extends {
source: objectsource: object; shadowDOM?: booleanshadowDOM?: boolean; onError?: (error: Error | string) => voidonError?: (error: string | Errorerror: Error | string) => void; debug?: booleandebug?: boolean; }>( props: T extends { source: object; shadowDOM?: boolean; onError?: (error: Error | string) => void; debug?: boolean; }props:
function (type parameter) T in sandbox<T extends {
    source: object;
    shadowDOM?: boolean;
    onError?: (error: Error | string) => void;
    debug?: boolean;
}>(props: T, events?: SandboxEvents, hostBridge?: HostBridge): ArrowTemplate
T
,
events: SandboxEventsevents?: SandboxEvents, hostBridge: HostBridgehostBridge?:
type HostBridge = {
    [x: string]: HostBridgeModule;
}
HostBridge
): ArrowTemplate

Rules

  • source must contain exactly one main.ts or main.js entry file.
  • main.css is optional and is injected into the sandbox host root. By default that root is an open shadow root.
  • Pass shadowDOM: false to render into the custom element’s light DOM instead.
  • Use the optional second argument to receive output(payload) calls from inside the sandbox.
  • Use the optional third argument to expose host bridge modules that sandbox code can import directly.

Usage

import { function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml } from '@arrow-js/core'
import { 
function sandbox<T extends {
    source: object;
    shadowDOM?: boolean;
    onError?: (error: Error | string) => void;
    debug?: boolean;
}>(props: T, events?: SandboxEvents, hostBridge?: HostBridge): ArrowTemplate
sandbox
} from '@arrow-js/sandbox'
const
const source: {
    'main.ts': string;
}
source
= {
'main.ts': [ "import { html, reactive } from '@arrow-js/core'", "import { formatCount } from 'host-bridge:demo'", '', 'const state = reactive({ count: 0 })', '', 'export default html`<button @click="${() => state.count++}">', ' ${() => formatCount(state.count)}', '</button>`', ].Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
('\n'),
} function html(strings: TemplateStringsArray | string[], ...expressions: unknown[]): ArrowTemplatehtml`<main>${
sandbox<{
    source: {
        'main.ts': string;
    };
}>(props: {
    source: {
        'main.ts': string;
    };
}, events?: SandboxEvents, hostBridge?: HostBridge): ArrowTemplate
sandbox
({
source: {
    'main.ts': string;
}
source
}, {
SandboxEvents.output?: (payload: unknown) => voidoutput(payload: unknownpayload) { var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(payload: unknownpayload)
}, }, { 'host-bridge:demo': { function formatCount(count: unknown): stringformatCount(count: unknowncount) { return 'Count ' + count: unknowncount }, }, })}</main>`
Security Model

User-authored Arrow code runs inside QuickJS/WASM. The host page only mounts trusted DOM and forwards sanitized event payloads. It does not run user callbacks in the window realm.

Type Reference

All types are exported from their respective packages. Import them with the type keyword for type-only imports.

@arrow-js/core

export type ParentNode = Node | DocumentFragment

export interface ArrowTemplate {
  (parent: ParentNode): ParentNode
  (): DocumentFragment
  isT: boolean
  key: (key: ArrowTemplateKey) => ArrowTemplate
  id: (id: ArrowTemplateId) => ArrowTemplate
  _c: () => Chunk
  _k: ArrowTemplateKey
  _i?: ArrowTemplateId
}

export type ArrowTemplateKey = string | number | undefined
type ArrowTemplateId = string | number | undefined

export type ArrowRenderable =
  | string
  | number
  | boolean
  | null
  | undefined
  | ComponentCall
  | ArrowTemplate
  | Array<string | number | boolean | ComponentCall | ArrowTemplate>

export type ArrowFunction = (...args: unknown[]) => ArrowRenderable

export type ArrowExpression =
  | ArrowRenderable
  | ArrowFunction
  | EventListener
  | ((evt: InputEvent) => void)

export type ReactiveTarget = Record<PropertyKey, unknown> | unknown[]

interface ReactiveAPI<T> {
  $on: <P extends keyof T>(p: P, c: PropertyObserver<T[P]>) => void
  $off: <P extends keyof T>(p: P, c: PropertyObserver<T[P]>) => void
}

export interface Computed<T> extends Readonly<Reactive<{ value: T }>> {}

type ReactiveValue<T> = T extends Computed<infer TValue>
  ? TValue
  : T extends ReactiveTarget
    ? Reactive<T> | T
    : T

export type Reactive<T extends ReactiveTarget> = {
  [P in keyof T]: ReactiveValue<T[P]>
} & ReactiveAPI<T>

export interface PropertyObserver<T> {
  (newValue?: T, oldValue?: T): void
}

export type Props<T extends ReactiveTarget> = {
  [P in keyof T]: T[P] extends ReactiveTarget ? Props<T[P]> | T[P] : T[P]
}
export type EventMap = Record<string, unknown>

export type EventMap = Record<string, unknown>

export type Events<T extends EventMap> = {
  [K in keyof T]?: (payload: T[K]) => void
}

export type Emit<T extends EventMap> = <K extends keyof T>(
  event: K,
  payload: T[K]
) => void

export type ComponentFactory = (
  props?: Props<ReactiveTarget>,
  emit?: Emit<EventMap>
) => ArrowTemplate

export interface AsyncComponentOptions<
  TProps extends ReactiveTarget,
  TValue,
  TEvents extends EventMap = EventMap,
  TSnapshot = TValue,
> {
  fallback?: unknown
  onError?: (
    error: unknown,
    props: Props<TProps>,
    emit: Emit<TEvents>
  ) => unknown
  render?: (
    value: TValue,
    props: Props<TProps>,
    emit: Emit<TEvents>
  ) => unknown
  serialize?: (
    value: TValue,
    props: Props<TProps>,
    emit: Emit<TEvents>
  ) => TSnapshot
  deserialize?: (snapshot: TSnapshot, props: Props<TProps>) => TValue
  idPrefix?: string
}

export interface ComponentCall {
  h: ComponentFactory
  p: Props<ReactiveTarget> | undefined
  e: Events<EventMap> | undefined
  k: ArrowTemplateKey
  key: (key: ArrowTemplateKey) => ComponentCall
}

export interface Component<TEvents extends EventMap = EventMap> {
  (props?: undefined, events?: Events<TEvents>): ComponentCall
}

export interface ComponentWithProps<
  T extends ReactiveTarget,
  TEvents extends EventMap = EventMap,
> {
  <S extends T>(props: S, events?: Events<TEvents>): ComponentCall
}

@arrow-js/framework

export interface RenderPayload {
  RenderPayload.async: Record<string, unknown>async: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, unknown>
RenderPayload.boundaries: string[]boundaries: string[] } export interface RenderResult { RenderResult.root: ParentNoderoot: ParentNode RenderResult.template: ArrowTemplatetemplate: ArrowTemplate RenderResult.payload: RenderPayloadpayload: RenderPayload } export interface BoundaryOptions { BoundaryOptions.idPrefix?: stringidPrefix?: string } export interface DocumentRenderParts { DocumentRenderParts.head?: stringhead?: string DocumentRenderParts.html: stringhtml: string DocumentRenderParts.payloadScript?: stringpayloadScript?: string }

@arrow-js/ssr

export interface HydrationPayload {
  HydrationPayload.html?: stringhtml?: string
  HydrationPayload.rootId?: stringrootId?: string
  HydrationPayload.async?: Record<string, unknown>async?: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, unknown>
HydrationPayload.boundaries?: string[]boundaries?: string[] } export interface SsrRenderOptions { SsrRenderOptions.rootId?: stringrootId?: string } export interface SsrRenderResult { SsrRenderResult.html: stringhtml: string SsrRenderResult.payload: HydrationPayloadpayload: HydrationPayload }

@arrow-js/hydrate

export interface HydrationMismatchDetails {
  HydrationMismatchDetails.actual: stringactual: string
  HydrationMismatchDetails.expected: stringexpected: string
  HydrationMismatchDetails.mismatches: numbermismatches: number
  HydrationMismatchDetails.repaired: booleanrepaired: boolean
  HydrationMismatchDetails.boundaryFallbacks: numberboundaryFallbacks: number
}

export interface HydrationOptions {
  HydrationOptions.onMismatch?: (details: HydrationMismatchDetails) => voidonMismatch?: (details: HydrationMismatchDetailsdetails: HydrationMismatchDetails) => void
}

export interface HydrationResult {
  HydrationResult.root: ParentNoderoot: type ParentNode = Node | DocumentFragmentParentNode
  HydrationResult.template: ArrowTemplatetemplate: ArrowTemplate
  HydrationResult.payload: RenderPayloadpayload: RenderPayload
  HydrationResult.adopted: booleanadopted: boolean
  HydrationResult.mismatches: numbermismatches: number
  HydrationResult.boundaryFallbacks: numberboundaryFallbacks: number
}

@arrow-js/sandbox

export interface SandboxProps {
  source: Record<string, string>
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}

export interface SandboxEvents {
  output?: (payload: unknown) => void
}

export function sandbox<T extends {
  source: object
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}>(
  props: T,
  events?: SandboxEvents,
  hostBridge?: HostBridge
): ArrowTemplate {
  ensureSandboxElement()
  return html`${SandboxHostComponent({
    config: props as SandboxHostProps,
    events,
    hostBridge,
  })}`
}