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

@pdf-block/core

v0.2.0

Published

The pure document engine behind @pdf-block/react: DSL types, block factories, normalization, migration, validation and editor commands as pure functions. Headless — no React, no DOM.

Readme

@pdf-block/core

O motor puro de documentos do pdf-block: os tipos da DSL, as fábricas de bloco, a normalização, a migração v2 → v3, a validação e os comandos que o editor executa — como funções sobre o documento. Headless: não importa React, DOM, TipTap, Zustand nem nenhuma lib de UI, e roda em Node, num worker ou numa CLI.

É o mesmo código que o @pdf-block/react usa por dentro (ele depende deste pacote). Use-o direto quando precisar montar, migrar, validar ou alterar um documento fora do editor — num backend, num job, num teste ou num agente de IA.

Versão 0.x — superfície ainda instável. O core estreia em 0.1.0 e nomes ou assinaturas podem mudar num minor (0.1 → 0.2) até a fase do servidor MCP. Dependendo do core direto, fixe a faixa (~0.1.0). Quem usa só o @pdf-block/react não é afetado.

Instalação

npm install @pdf-block/core
# ou
pnpm add @pdf-block/core

Dependências de runtime: apenas uuid e immer. Não há peerDependencies. O pacote publica ESM (import) e CommonJS (require), com tipos para os dois (bundler, NodeNext e Node16).

Node ≥ 20.19 (ou ≥ 22.12 na linha 22; engines.node declara >=20.19.0): o uuid@13 é só ESM, e o require('@pdf-block/core') depende do require() de ESM que essas versões trazem. Com import não há essa exigência além do suporte a ESM.

O que contém

  • Tipos da DSL — DocumentV3, Section, FlowNode, Group, ColumnSet, os blocos (TextBlock, TableBlock, …), BlockStyles, VariableDef, NamedTheme e o modelo v2 (Document), que só entra pelo importador.

  • Fábricas — createContentBlock(type) (folha com os defaults do tipo), createGroupV3, createColumnSetV3, createBannerGroupV3, createDefaultDocument, e registerCustomBlockDefaults(type, defaultProps) para que um tipo de plugin nasça CustomBlock sem React. O registro é estado do módulo: duas cópias do core no mesmo processo (require e import misturados, ou duas versões sem deduplicar) são dois registros, e createContentBlock numa cópia não vê o plugin registrado na outra. Use um só formato de import e uma só versão (npm ls @pdf-block/core).

  • Normalização, migração e validação — resolveLoadedDocument (aceita v2 ou v3 e devolve v3 normalizado), normalizeDocumentV3, migrateV2toV3, isDocumentV3, validateDocument e serializeIssue (a issue sem a função de correção, pronta para JSON).

  • Portabilidade de template — templateCompat, mergeVariablesIntoTemplate, namedBlockCompat, transplantNamedBlocks.

  • Temas e variáveis — getActiveTheme, projectActiveTheme, resolveThemeDeep, resolveBindingsDeep, applyFormat(value, format) (formata um valor de variável — 'date:dd/MM/yyyy', 'currency:BRL'… — e devolve a string; não confundir com o pincel applyFormatInDoc), as séries de lista (seriesIn, materializeSeries, reconcileItemScopes, dissolveSeries) e o índice do fluxo (buildFlowIndex, FlowPath).

  • Comandos puros — as ações do editor como <ação>InDoc(doc, …): fluxo e seções (addFlowLeafInDoc, placeFlowNodesInDoc, moveFlowNodeInDoc, …), colunas, estilos e o pincel de formato (applyFormatInDoc(doc, targetId, brush): copia a caixa e os campos próprios do retrato lido por readFormatBrushPayload para outro bloco), variáveis e listas, temas, cabeçalho/rodapé e documento. Cada comando muta o documento recebido. O retorno, na maioria, é boolean (houve mudança?) ou o id criado (string | null, null = recusa). Exceções:

    • os comandos de variáveis devolvem um OBJETO — VariablesResult { variablesChanged } ou, nos que criam, VariablesCreateResult { variablesChanged, id }. O objeto é sempre verdadeiro: teste result.variablesChanged (e result.id), nunca o result em si;
    • addThemeInDoc, applyLibraryThemeInDoc, addNamedStyleInDoc e addSectionInDoc devolvem SEMPRE o id (string), sem caso de recusa;
    • removeFlowNodeInDoc devolve o objeto { removed, listTouched } — teste removed.

    applyToDocument(doc, recipe) aplica qualquer um deles a um documento imutável e devolve { doc, result }. O doc devolvido sai congelado pelo immer (autoFreeze): chamar um <ação>InDoc direto nele lança erro — encadeie outro applyToDocument ou passe o seu próprio clone mutável (structuredClone(doc)).

O que não contém

  • O editor, os componentes, os hooks e o store (zustand) — ficam no @pdf-block/react.
  • Histórico de desfazer, seleção e callbacks (onDocumentChange): isso é do store do editor, que chama os mesmos comandos.
  • Paginação visual, renderização em HTML e geração de PDF — o PDF sai do pdf-block/laravel (servidor).
  • Textos traduzidos: a validação devolve chaves i18n (messageKey); quem desenha resolve com o t(messageKey, messageParams) do @pdf-block/react.

Exemplo em Node

import {
  addFlowLeafInDoc,
  applyToDocument,
  createDefaultGlobalStyles,
  createDefaultPageSettings,
  resolveLoadedDocument,
  serializeIssue,
  upsertVariableInDoc,
  validateDocument,
} from '@pdf-block/core';

// Documento novo, já no modelo canônico v3.
let doc = resolveLoadedDocument(undefined, createDefaultPageSettings(), createDefaultGlobalStyles());
const sectionId = doc.sections[0].id;

// Cada comando muta um RASCUNHO; o documento de entrada fica intacto.
const upserted = applyToDocument(doc, (draft) =>
  upsertVariableInDoc(draft, { key: 'cliente', label: 'Cliente', value: 'ACME' }),
);
doc = upserted.doc; // upserted.result.variablesChanged: false = recusa (chave inválida)
const added = applyToDocument(doc, (draft) => addFlowLeafInDoc(draft, [sectionId], 'text'));
doc = added.doc; // added.result é o id do bloco, ou null se a inserção foi recusada

const { ok, issues } = validateDocument(doc, { variables: doc.variables });
console.log(ok, issues.map(serializeIssue));

O documento resultante é o JSON que o PDFBuilder carrega e que o pdf-block/laravel renderiza.

Licença

MIT