npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rtif-sdk/engine

v2.14.0

Published

RTIF editor engine, history, and plugin host

Readme

@rtif-sdk/engine

Editor engine for RTIF (Rich Text Input Format). Manages document state, dispatches operations through a plugin lifecycle, and provides undo/redo history.

Install

npm install @rtif-sdk/engine

Usage

Create an engine

import { createEngine } from '@rtif-sdk/engine';
import type { Document } from '@rtif-sdk/core';

const doc: Document = {
  version: 1,
  blocks: [{ id: 'b1', type: 'text', spans: [{ text: '' }] }],
};

const engine = createEngine(doc);

Dispatch operations

engine.dispatch({ type: 'insert_text', offset: 0, text: 'Hello' });

console.log(engine.state.doc.blocks[0].spans[0].text); // 'Hello'

Undo / Redo

engine.undo(); // reverts to empty document
engine.redo(); // re-applies 'Hello'

Subscribe to changes

const unsubscribe = engine.onChange((state) => {
  console.log('Document changed:', state.doc);
});

Plugins

import type { Plugin } from '@rtif-sdk/engine';

const myPlugin: Plugin = {
  id: 'my-plugin',
  init(engine) {
    engine.registerCommand('greet', (eng) => {
      eng.dispatch({ type: 'insert_text', offset: 0, text: 'Hi! ' });
    });
  },
};

engine.use(myPlugin);
engine.exec('greet');

Plugin Lifecycle

  1. beforeApply hooks — inspect, modify, or cancel operations
  2. apply() — core applies operations to the document
  3. afterApply hooks — observe changes, trigger side effects
  4. onChange listeners — UI re-renders

Plugin errors are caught and isolated — a failing plugin never breaks the editor.

Transactions

Group multiple dispatches into a single undo entry. Useful for multi-step edits that should undo as one unit.

const tx = engine.transaction();

engine.dispatch({ type: 'delete_text', offset: 0, count: 5 });
engine.dispatch({ type: 'insert_text', offset: 0, text: 'Hello' });

tx.commit(); // Single undo group

Snapshot-based undo

For complex replacements where composed inverse ops are fragile, commit with useSnapshot: true to capture the pre-transaction document state and restore it on undo:

const tx = engine.transaction();
// ... many dispatches ...
tx.commit({ useSnapshot: true }); // Undo restores exact pre-transaction state

Transaction API

interface Transaction {
  readonly isOpen: boolean;
  commit(options?: TransactionCommitOptions): void;
  rollback(): void;
}

interface TransactionCommitOptions {
  useSnapshot?: boolean;
}

Dispatching while a transaction from a different source is already open throws RtifError('TRANSACTION_ACTIVE').

Streaming Plugin

Replace a range of content incrementally (e.g., AI-generated text streaming in). Built on transactions for atomic undo.

import { streamingPlugin, StreamingCommands } from '@rtif-sdk/engine';

const { plugin, startStream } = streamingPlugin();
engine.use(plugin);

const session = startStream(engine, {
  startOffset: 0,
  endOffset: 10,
  onSession(s) { /* streaming started */ },
  onCommit() { /* streaming finished */ },
  onCancel() { /* streaming cancelled */ },
});

// Append text chunks
session.append({ text: 'Hello ' });
session.append({ text: 'world', marks: { bold: true } });
session.append({ text: '', blockBreak: true }); // New block

// Or replace with fully deserialized blocks
session.replaceContent(deserializedBlocks);

session.commit();  // Finalize (single undo group)
// or session.cancel(); // Roll back everything

StreamingChunk

interface StreamingChunk {
  text: string;
  marks?: Record<string, unknown>;
  blockBreak?: boolean;
  blockType?: string;
  blockAttrs?: Record<string, unknown>;
}

StreamingConfig

interface StreamingConfig {
  startOffset: number;
  endOffset: number;
  onSession?: (session: StreamingSession) => void;
  onCommit?: () => void;
  onCancel?: () => void;
}

Stream Insert (Cursor-Position)

Insert new content at the cursor without requiring a selection. The LLM's output controls all block types, marks, and structure — no boundary corrections are applied.

import { createStreamInsert } from '@rtif-sdk/engine';

// Start session at cursor (startOffset === endOffset — no deletion)
const session = streaming.startStream(engine, {
  startOffset: cursor, endOffset: cursor,
});

const insert = createStreamInsert({ session, deserialize });

for await (const token of llm.stream(prompt)) {
  insert.pushToken(token);
}
insert.commit();

Stream Rewrite (Selection Range)

Replace a selected range with boundary-aware corrections. Preserves the prefix block type when the selection starts mid-block, and splits the suffix when types differ.

import { computeSelectionBoundaries, createStreamRewrite } from '@rtif-sdk/engine';

// 1. Capture boundaries before deletion
const boundaries = computeSelectionBoundaries(doc, start, end);
// 2. Start session (deletes original range)
const session = streaming.startStream(engine, { startOffset: start, endOffset: end });
// 3. Wrap with boundary corrections
const rewrite = createStreamRewrite({ session, engine, deserialize, boundaries });

for await (const token of llm.stream(prompt)) {
  rewrite.pushToken(token);
}
rewrite.commit();

StreamSession (Generic)

Both StreamInsertSession and StreamRewriteSession extend the shared StreamSession interface. Use it when handling either mode generically:

import type { StreamSession } from '@rtif-sdk/engine';

let stream: StreamSession;
if (hasSelection) {
  stream = createStreamRewrite({ ... });
} else {
  stream = createStreamInsert({ ... });
}

Format Stream Adapter

Bridge a format deserializer (e.g., markdown, HTML) for incremental streaming. Each token push re-deserializes the accumulated text and replaces the streamed content.

import { createStreamingAdapter } from '@rtif-sdk/engine';
import { deserialize } from '@rtif-sdk/format-markdown';

const adapter = createStreamingAdapter(deserialize);

// As tokens arrive from an LLM:
for (const token of tokens) {
  const blocks = adapter.push(token); // Re-deserializes accumulated text
  session.replaceContent(blocks);
}

session.commit();

Additional Exports

| Export | Description | |--------|-------------| | blockMovePlugin | Engine plugin that registers block-move operations and keyboard shortcuts. | | BlockMoveCommands | Command name constants for block move (BlockMoveCommands.MOVE_UP, BlockMoveCommands.MOVE_DOWN). | | computeMoveBlockOps(doc, blockId, direction) | Returns the composed operation array to move a block up or down without a new core op. | | computeAttrsDiff(prev, next) | Returns the minimal attrs patch (with null removals) needed to transform prev into next. Used internally by move ops; useful for custom block-mutation commands. | | streamingPlugin() | Factory returning { plugin, startStream } for streaming content replacement. | | StreamingCommands | Command name constants (START, CANCEL). | | createStreamingAdapter(deserialize) | Creates a format-aware streaming adapter for incremental re-deserialization. | | createStreamInsert({ session, deserialize }) | Cursor-position insert — no boundary corrections. | | createStreamRewrite({ session, engine, deserialize, boundaries }) | Selection-range rewrite with boundary-aware corrections. | | computeSelectionBoundaries(doc, start, end) | Compute block-type boundary metadata for rewrite sessions. | | buildRewriteContext(doc, start, end, serialize) | Build LLM rewrite context (boundaries + serialized selection). | | buildRewritePrompt({ selected, instruction, ... }) | Optional prompt builder for LLM rewrites. | | extractRtifSlice(doc, start, end) | Extract a slice of blocks/spans from a document range. | | deleteSelectionOps(doc, start, end) | Compute delete_text + merge_block ops for a cross-block range. |

License

MIT