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