@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.
Maintainers
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/reactnão é afetado.
Instalação
npm install @pdf-block/core
# ou
pnpm add @pdf-block/coreDependê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,NamedThemee o modelo v2 (Document), que só entra pelo importador.Fábricas —
createContentBlock(type)(folha com os defaults do tipo),createGroupV3,createColumnSetV3,createBannerGroupV3,createDefaultDocument, eregisterCustomBlockDefaults(type, defaultProps)para que um tipo de plugin nasçaCustomBlocksem React. O registro é estado do módulo: duas cópias do core no mesmo processo (requireeimportmisturados, ou duas versões sem deduplicar) são dois registros, ecreateContentBlocknuma 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,validateDocumenteserializeIssue(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 pincelapplyFormatInDoc), 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 porreadFormatBrushPayloadpara 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: testeresult.variablesChanged(eresult.id), nunca oresultem si; addThemeInDoc,applyLibraryThemeInDoc,addNamedStyleInDoceaddSectionInDocdevolvem SEMPRE o id (string), sem caso de recusa;removeFlowNodeInDocdevolve o objeto{ removed, listTouched }— testeremoved.
applyToDocument(doc, recipe)aplica qualquer um deles a um documento imutável e devolve{ doc, result }. Odocdevolvido sai congelado pelo immer (autoFreeze): chamar um<ação>InDocdireto nele lança erro — encadeie outroapplyToDocumentou passe o seu próprio clone mutável (structuredClone(doc)).- os comandos de variáveis devolvem um OBJETO —
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 ot(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
