Extensions
Extensions are host plug-ins that attach their own state to a Nu runtime. They let you store editor state, plug-in configuration, or any host-specific data alongside the document. An extension's state is split in two: serializable state lives in the document — so it participates in history and collaboration — while volatile state lives off-document and is never persisted.
Defining an extension
You define an extension with createExtension, give it a unique key, declare its initial state, and optionally provide init and dispose hooks. Register it on the runtime through Nu.create, then read it back with nu.getExtension.
import { createExtension, Nu } from '@nuforge/core';
import * as t from '@nuforge/types';
const counter = createExtension<{ count: number; volatile: { ticks: number } }>({
key: 'counter',
state: { count: 0, volatile: { ticks: 0 } },
init(ext) {
// setup; read/write ext.state.count and ext.state.volatile.ticks
},
dispose(ext) {},
});
const nu = Nu.create({ extensions: [counter] });
nu.load(t.state({ program: t.program({}) }));
const ext = nu.getExtension(counter);
The type parameter describes the shape of the extension's state. The init hook runs when the extension is set up and is where you wire things together; dispose runs on teardown.
Serializable state
Anything under state except the volatile key is serializable. It is stored in the AST under state.extensions[<key>].value, so it participates in undo/redo and collaboration just like any other document data. Because it is part of the document, you must mutate it inside nu.change:
nu.change(() => {
ext.state.count = 5; // persisted + undoable + synced
});
That write is persisted in the document, recorded as an undoable step, and synced to collaborators.
Volatile state
The volatile sub-object is the exception: it lives in nu.volatile[<key>] and is never serialized. Use it for ephemeral, off-document data — caches, transient UI flags, counters — that should not be persisted, undone, or synced. You mutate it directly, without nu.change:
ext.state.volatile.ticks++; // off-document, ephemeral
Subscribing to changes
To react when extension state changes, use ext.subscribe. You pass a collector that selects the value you care about and a callback that runs whenever that collected value changes.
const dispose = ext.subscribe(
(state) => state.count,
(count, prev) => {
// runs when the collected value changes
},
);
The first function collects a value from the extension state; the second runs with the new and previous values whenever the collected value changes. Call the returned dispose to stop listening.
History
Extension setup is excluded from history. Loading a document that registers extensions does not create an undo step — only the writes you make later inside nu.change are recorded. This keeps the undo stack clean and focused on user-meaningful edits.
Next steps
- Runtime & Core API — the
Nuobject, mutations, and history. - Collaboration — how serializable state syncs between peers.