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

@phuong-tran-redoc/document-engine-core

v0.1.9

Published

Framework-agnostic core library for Document Engine

Readme

document-engine-core

npm core License

A framework-agnostic document editor core built on Tiptap and ProseMirror. It packages the custom extensions, nodes, schema kit, and headless helpers that power the Document Engine, with no framework dependency.


🎯 Overview

document-engine-core is the heart of the Document Engine. It contains the editor schema (the canonical extension/node set), business extensions (dynamic fields, restricted editing, styled tables, indentation, text case), a headless HTML serializer, and a small document-migration system — all as plain ESM TypeScript that runs in the browser or in Node.

Key Features

  • Framework-agnostic ESM: pure TypeScript, usable from Angular, React, Vue, or headless Node.
  • Ready-made schema kit: defaultExtensions — the canonical node/mark/structure set the editor ships with.
  • Business extensions: Dynamic Fields ({{customer_name}}), Restricted Editing (editable regions), Styled Tables, Indent, Text Case, custom Ordered List.
  • Headless serialization: generateHTML(doc) renders a ProseMirror JSON document to HTML on the server (env-aware) or in the browser.
  • Document migrations: EditorDocument wrapper + migrateDoc() to version and upgrade stored content.
  • JSON-first data model: documents are ProseMirror JSON, not raw HTML.

📦 Installation

npm install @phuong-tran-redoc/document-engine-core
# or
pnpm add @phuong-tran-redoc/document-engine-core

Published publicly on npm under the MIT license.

Peer Dependencies

Only one peer dependency — Tiptap/ProseMirror ship as bundled dependencies and are installed automatically:

{
  "lodash-es": "^4.17.10"
}

Built against @tiptap/* ^3.26.0 (bundled). If your app also uses Tiptap directly, align on the same major to share a single ProseMirror instance.


🚀 Quick Start

Build an editor with the default schema

import { Editor } from '@tiptap/core';
import { defaultExtensions } from '@phuong-tran-redoc/document-engine-core';

const editor = new Editor({
  element: document.querySelector('#editor')!,
  extensions: [...defaultExtensions],
  content: '<p>Hello World!</p>',
});

Dynamic fields

// Insert a {{customer_name}} placeholder
editor.commands.insertDynamicField({ fieldId: 'customer_name', label: 'Customer Name' });
// Renders: <span data-field-id="customer_name" data-label="Customer Name" class="dynamic-field">{{customer_name}}</span>

Restricted editing (editable regions)

// Wrap the current selection in an editable region
editor.commands.wrapSelectionInEditableRegion();
editor.commands.toggleEditableRegion();
editor.commands.removeEditableRegion();

Other custom commands

editor.commands.insertPageBreak();                      // PageBreak node
editor.commands.insertImageRef({ imageId: 'logo-01' }); // URL-free image reference
editor.commands.textCase('uppercase');                  // TextCase: 'uppercase' | 'lowercase' | 'capitalize'
editor.commands.increaseIndent();                       // Indent
editor.commands.setListStyle('lower-alpha');            // CustomOrderedList

Headless HTML (server-side)

import { generateHTML, defaultExtensions } from '@phuong-tran-redoc/document-engine-core';

// generateHTML(doc: JSONContent, extensions = defaultExtensions): Promise<string>
const html = await generateHTML(myProseMirrorJSON);

CKEditor 5 HTML compatibility

Load HTML stored by the legacy CKEditor 5 build and save it back byte-identical: unchanged parts keep their exact markup, only what the user edited changes.

import { CkCompat, fromCkHtml, toCkHtml, verifyCkRoundTrip, defaultExtensions } from '@phuong-tran-redoc/document-engine-core';

const editor = new Editor({ extensions: [...defaultExtensions, CkCompat] });
const { html, wrapperClass, unsupported } = fromCkHtml(storedHtml); // `unsupported`: CKEditor plugin content with no editor node
editor.commands.setContent(html, { emitUpdate: false });

const saved = toCkHtml(editor.getHTML(), { wrapperClass: wrapperClass ?? false });
verifyCkRoundTrip(storedHtml, saved); // before editing: is the document reproduced exactly?

CKEditor restricted-editing exceptions (<span class="restricted-editing-exception">) load as editable regions when EditableRegion and RestrictedEditing are registered, and are saved back in that markup. In restricted mode, set content with editor.chain().setMeta('restrictedEditing', { allow: true }).setContent(html).run() — otherwise the restriction blocks the change once the document is not empty.

A document stored as several CKEditor wrappers side by side (sections joined after CKEditor saved them) loads as one document. wrapperClass is the first wrapper's class; the block that opens each later section records its wrapper (and the whitespace stored before it), and toCkHtml splits the document there again. Deleting that block merges its section into the one before; a section whose content is all deleted loses its wrapper, so when that is the first section the saved document starts with the next section's wrapper class. A pasted or dropped copy of an opening block does not open a section. A section that opens with a numbered list cannot be split again, so such a document keeps the wrappers it had (and does not round-trip).

On a backend (no DOM) pass a parser. createCkDomParser() uses happy-dom, which @tiptap/html already installs:

const domParser = await createCkDomParser();
const ckHtml = toCkHtml(await generateHTML(json, extensions), { wrapperClass, domParser });

Versioned documents + migrations

import { migrateDoc, LATEST_SCHEMA_VERSION, type EditorDocument } from '@phuong-tran-redoc/document-engine-core';

const stored: EditorDocument = { schemaVersion: 0, content: myProseMirrorJSON };
const upgraded = migrateDoc(stored); // walks registered migrations up to LATEST_SCHEMA_VERSION

📦 Public API

Everything below is re-exported from the package entry (@phuong-tran-redoc/document-engine-core).

Extensions (extensions)

| Export | Notable commands | | --- | --- | | ClearContent | clear() | | Indent | increaseIndent(), decreaseIndent() | | CustomOrderedList | toggleOrderedList(), setListStyle(type) | | ResetFormat | resetFormat() | | ResetOnEnter | — (Enter-key behavior) | | RestrictedEditing (+ EditableRegion) | wrapSelectionInEditableRegion(), toggleEditableRegion(), removeEditableRegion() | | StyledTable / StyledTableKit (+ StyledTableCell, StyledTableHeader) | insertTable(), setTableBorder(), setTableBackgroundColor(), setCellBackgroundColor(), setCellTextAlign(), … | | TextCase | textCase(type) |

Nodes (nodes)

| Export | Renders | | --- | --- | | DynamicField | inline <span data-field-id …>{{fieldId}}</span> — insertDynamicField({ fieldId, label }) | | PageBreak | <div data-page-break="true" …> — insertPageBreak() | | ImageRef | <figure data-block="image-ref" data-image-id …> (+ optional <figcaption>) — insertImageRef({ imageId, caption?, captionPosition? }) | | NotumHeading | extends Tiptap Heading — removeHeading() |

Kit (kit)

  • defaultExtensions: Extensions — the canonical schema (nodes, marks, structures).
  • generateHTML(doc, extensions?): Promise<string> — async, environment-aware headless serializer.

CKEditor compatibility (compat)

  • CkCompat — extension that carries the original CKEditor attributes through editing.
  • fromCkHtml(html, { domParser? }) → { html, wrapperClass, unsupported }; toCkHtml(html, { wrapperClass?, domParser? }).
  • verifyCkRoundTrip(original, saved), CK_UNSUPPORTED_CONTENT, createCkDomParser().

Migrations (migrations)

  • EditorDocument — { schemaVersion: number; content: JSONContent }.
  • LATEST_SCHEMA_VERSION — current schema version.
  • docMigrations — the migration registry.
  • migrateDoc(doc, migrations?, latest?) — pure upgrade walker.

Models, types, utils, views

  • models: Color (Color.from(), .equals(), .is()).
  • types: ListStyleType, TextCaseType, ImageRefAttributes, DynamicFieldAttributes, DynamicFieldItem, DynamicFieldCategory, …
  • utils: normalizeColor (color), getClosestDomElement (dom), getCursorCellInfo / getSelectedCells (table), getSelectedText / getActiveMarkRange (text).
  • views: HandleNodeView, TableNodeView, PageBreakNodeView, createTableNodeView, createPageBreakNodeView.
  • constants: INDENT_DEFAULT.

The package entry index.ts is the public contract — additive changes only between minor versions.


🏗️ Source Layout

document-engine-core/src/
├── constants/    # exported constants (e.g. INDENT_DEFAULT)
├── extensions/   # custom Tiptap extensions
├── kit/          # defaultExtensions + generateHTML
├── migrations/   # EditorDocument + migrateDoc
├── models/       # Color, …
├── nodes/        # DynamicField, PageBreak, ImageRef, NotumHeading
├── types/        # shared TypeScript types
├── utils/        # color / dom / table / text helpers
└── views/        # ProseMirror node views

🔧 Development

nx build @phuong-tran-redoc/document-engine-core   # build the library
nx test @phuong-tran-redoc/document-engine-core    # unit tests
nx lint @phuong-tran-redoc/document-engine-core    # lint

🎨 Framework Wrappers


📖 Resources


👤 Authors

See AUTHORS.md.


📄 License

MIT License — see LICENSE.md.