jsegd-bpm
v0.1.7
Published
Biblioteca runtime para desenvolvimento EGD BPM — EGD Tecnologia
Downloads
980
Readme
jsEGD BPM
Biblioteca TypeScript de runtime para desenvolvimento de formulários e workflows no ambiente EGD BPM — EGD Tecnologia.
Fornece componentes de anexo, gerenciamento de workers para datasets, controle do ciclo de vida do workflow e utilitários de formulário integrados ao DOM do EGD BPM.
Dependências par obrigatórias:
jsegd≥ 1.1.0 ealpinejs≥ 3.0.0
Instalação
npm install jsegd-bpm jsegd alpinejsEntradas do pacote
| Entrada | Importação | Conteúdo |
| --------- | ------------- | ----------------------------------------------------------------------------- |
| Principal | jsegd-bpm | Workers, anexos, validação, workflow, form-repository, workflow-view e alpine |
WorkerManager — Consultas de Dataset em Web Workers
Gerencia um pool de Web Workers para executar consultas à API de Datasets do EGD BPM fora da thread principal. Suporta deduplicação de requisições, cache em IndexedDB e cancelamento HTTP real via AbortController.
import { WorkerManager, ConstraintsType } from 'jsegd-bpm'
import { LocalCache } from 'jsegd'
// Compartilhando um cache existente entre componentes:
const cache = LocalCache.getInstance()
cache.initialize({ storeName: 'datasets', cacheExpirationMs: 60 * 60 * 1000 })
const manager = new WorkerManager({ poolSize: 4, cache })
const { promise, cancel } = manager.query('https://tenant.bpm.empresa.com', {
datasetId: 'dsColaboradores',
field: ['nome', 'email', 'matricula'],
constraintsField: ['ativo'],
constraintsInitialValue: ['true'],
constraintsType: [ConstraintsType.MUST],
})
// Cancelar requisição em andamento, se necessário:
// cancel()
const result = await promise
console.log(result.columns) // ['nome', 'email', 'matricula']
console.log(result.values) // [{ nome: 'Ana', email: '[email protected]', matricula: '1234' }, ...]
// Liberar recursos ao desmontar o componente:
manager.terminate()WorkerManagerOptions
| Opção | Tipo | Padrão | Descrição |
| -------------- | -------------- | ------ | -------------------------------------------------------------- |
| poolSize | number | 4 | Número de workers paralelos |
| cache | ICache | — | Instância de cache existente (compartilhada entre componentes) |
| cacheOptions | CacheOptions | — | Cria um LocalCache interno com storeName e TTL em ms |
Se nem
cachenemcacheOptionsfor informado, o gerenciador opera sem cache.
ConstraintsType — enum: MUST, SHOULD, MUST_NOT.
AttachButton / AttachmentsTable — Componentes de Anexo
Custom Elements prontos para uso em formulários EGD BPM. Registre-os uma vez no ponto de entrada da aplicação.
import { AttachButton, AttachmentsTable } from 'jsegd-bpm'
customElements.define('attachment-button', AttachButton)
customElements.define('attachments-table', AttachmentsTable)<attachment-button>
Botão com dropdown para upload de arquivos vinculados ao processo.
<!-- Tipo de anexo fixo -->
<attachment-button description="CONTRATO"></attachment-button>
<!-- Prompt ao usuário para informar a descrição no upload -->
<attachment-button></attachment-button>
<!-- Somente leitura: oculta botão de adicionar -->
<attachment-button description="CONTRATO" noaddbutton></attachment-button>| Atributo | Tipo | Descrição |
| ---------------- | -------- | ---------------------------------------------------------------- |
| description | string | Tipo do anexo. Vazio abre modal para o usuário informar |
| accept | string | Filtro de tipos aceitos pelo seletor nativo (ex: ".pdf,.docx") |
| noaddbutton | — | Oculta o botão de adicionar |
| noviewbutton | — | Oculta o botão de visualizar |
| noeditbutton | — | Oculta o botão de editar descrição |
| nodeletebutton | — | Oculta o botão de excluir |
<attachments-table>
Tabela que lista os anexos do processo com suporte a drag-and-drop para reordenação.
<attachments-table mode="edit"></attachments-table>BeforeSendValidate — Validação com feedback visual
Integra validação de campos com o sistema de erros visuais do tema EGD BPM (data-group / data-helper).
import { BeforeSendValidate } from 'jsegd-bpm'
import type { FieldError } from 'jsegd-bpm'
function validarFormulario() {
const erros: FieldError[] = []
const campoNome = document.querySelector<HTMLInputElement>('[name="nome_solicitante"]')
if (!campoNome?.value) {
erros.push({ field: campoNome, errorMessage: 'Informe o nome do solicitante' })
}
if (erros.length > 0) {
BeforeSendValidate.throwValidationError(erros)
// Lança Error com HTML formatado para o widget de processo exibir
}
}throwValidationError lança sempre — use dentro de beforeSendValidate do EGD BPM ou em hooks de WorkflowViewPatcher.
WorkflowViewPatcher — Hooks no widget de processo
Substitui métodos do widget nativo ECM_WKFView para interceptar o ciclo de envio do processo sem modificar o EGD BPM.
import { WorkflowViewPatcher } from 'jsegd-bpm'
// 1. Inicializar — aguarda o widget carregar no frame pai
await WorkflowViewPatcher.init()
// 2. Aplicar todos os patches registrados
WorkflowViewPatcher.patchAll()
// 3. Registrar hooks
WorkflowViewPatcher.addBeforeValidate(async () => {
// Executado antes da validação do widget.
// Lançar exceção cancela o envio e reabilita os botões.
const valor = document.querySelector<HTMLInputElement>('[name="campo_critico"]')?.value
if (!valor) throw new Error('Campo crítico obrigatório')
})
WorkflowViewPatcher.addAfterValidate(async () => {
// Executado após validação bem-sucedida, antes do envio efetivo.
await salvarRascunho()
})
WorkflowViewPatcher.addOnMoveSuccess(() => {
console.log('Processo movimentado com sucesso')
})
WorkflowViewPatcher.addOnMoveError(() => {
console.error('Erro na movimentação — verifique os logs')
})
WorkflowViewPatcher.addOnCompletionLinkAdd((message) => {
// Adiciona links à tela de conclusão após a movimentação
message.links.push({ description: 'Abrir tarefa seguinte', href: '/minha-tarefa' })
})
// Ao desmontar o formulário (SPAs): restaura os métodos originais
WorkflowViewPatcher.restore()Métodos estáticos
| Método | Descrição |
| ---------------------------- | ------------------------------------------------------- |
| init(timeout?) | Aguarda ECM_WKFView ficar disponível no frame pai |
| patchAll() | Aplica todos os overrides registrados |
| patch(attr) | Aplica um override específico |
| restore() | Reverte todos os métodos e limpa hooks e flags internos |
| addBeforeValidate(fn) | Hook antes da validação do widget |
| addAfterValidate(fn) | Hook após validação, antes do envio |
| addOnMoveSuccess(fn) | Callback de sucesso na movimentação |
| addOnMoveError(fn) | Callback de erro na movimentação |
| addOnCompletionLinkAdd(fn) | Manipula links da tela de conclusão |
IFormRepository — Acesso tipado ao formulário
Interface agnóstica ao DOM com duas implementações: BpmFormRepository (produção) e MockFormRepository (testes unitários).
Convenção de nomenclatura de campos
Os campos do formulário EGD BPM devem seguir o padrão:
{domínio}_{atributo_snake_case}onde {domínio} é o identificador do agregado de negócio ao qual o campo pertence — não a tela em que aparece nem a fase do workflow. O total não deve ultrapassar 30 caracteres (limite de coluna no banco EGD).
| Campo no DOM | Domínio | Atributo no modelo |
| ------------------- | ----------- | ------------------ |
| contrato_indice | contrato | indice |
| imovel_cep | imovel | cep |
| vigencia_prazo | vigencia | prazo |
| fornecedor_cep | fornecedor| cep |
Importante: o
{domínio}é o prefixo persistido no banco pelo Fluig — é parte integrante do nome da coluna e não deve ser omitido no HTML. Oloade osavegerenciam a conversão entre o nome DOM e o atributo do modelo de forma transparente.
O moduleId do workflow (quem exibe o campo na tela) e o stage (em que fase do processo estamos) são independentes do domínio do campo — um mesmo módulo pode exibir campos de múltiplos domínios.
Produção — BpmFormRepository
Lê e escreve diretamente nos elementos input, select e textarea do formulário EGD BPM. Encapsula toda a interação com wdkAddChild para tabelas filhas.
load(section) remove o prefixo {section}_ e converte snake_case → camelCase antes de retornar.save(section, data) faz o caminho inverso: converte camelCase → snake_case e adiciona o prefixo ao escrever no DOM.
import { BpmFormRepository } from 'jsegd-bpm'
interface Contrato {
indice: string
primeiroPagamento: string
reajuste: string
}
interface Imovel {
cep: string
logradouro: string
municipio: string
}
interface ItemTabela {
descricao: string
valor: string
}
const repo = new BpmFormRepository()
// Ler domínio 'contrato' — lê campos com name="contrato_*" do DOM
// DOM: contrato_indice, contrato_primeiro_pagamento, contrato_reajuste
const contrato = repo.load<Contrato>('contrato')
console.log(contrato.indice) // valor de input[name="contrato_indice"]
console.log(contrato.primeiroPagamento) // valor de input[name="contrato_primeiro_pagamento"]
// Ler domínio 'imovel' — desambigua de 'fornecedor_cep', 'contrato_cep', etc.
const imovel = repo.load<Imovel>('imovel')
console.log(imovel.cep) // valor de input[name="imovel_cep"]
// Ler tabela filha
const itens = repo.loadTable<ItemTabela>('tabItens')
// Persistir no DOM — save converte camelCase → prefixo+snake_case
repo.save<Contrato>('contrato', { indice: 'IGPM', primeiroPagamento: '2026-02-01' })
// escreve: input[name="contrato_indice"] e input[name="contrato_primeiro_pagamento"]
// Substituir linhas da tabela filha
repo.saveTable<ItemTabela>('tabItens', [
{ descricao: 'Notebook', valor: '4500.00' },
{ descricao: 'Mouse', valor: '120.00' },
])Testes — MockFormRepository
Implementação in-memory sem dependência de DOM. Aplica a mesma lógica de prefixo e conversão de BpmFormRepository para que os testes reflitam o comportamento real.
import { MockFormRepository } from 'jsegd-bpm'
interface Vigencia {
prazo: string
tipoDePrazo: string
}
const repo = new MockFormRepository()
// save armazena internamente como 'vigencia_prazo', 'vigencia_tipo_de_prazo'
repo.save<Vigencia>('vigencia', { prazo: '12', tipoDePrazo: 'Meses' })
// load recupera { prazo: '12', tipoDePrazo: 'Meses' }
const data = repo.load<Vigencia>('vigencia')
// data.prazo === '12'
// data.tipoDePrazo === 'Meses'
repo.saveTable('tabItens', [{ descricao: 'Cadeira', valor: '800.00' }])
const itens = repo.loadTable('tabItens')
repo.reset() // limpa todos os dadosProcessos legados — loadFields
Processos existentes cujos campos não seguem a convenção {domínio}_{atributo} não devem ser alterados para evitar migração de dados no banco. Para esses casos, jsegd-bpm exporta a função utilitária loadFields, que recebe um mapeamento explícito { propriedadeModelo: nomeDOM }:
import { loadFields } from 'jsegd-bpm'
interface Solicitante {
nome: string
email: string
}
// Campo legado: sufixo em vez de prefixo — nome_solicitante, email_solicitante
const dados = loadFields<Solicitante>({
nome: 'nome_solicitante',
email: 'email_solicitante',
})
// dados.nome === valor de input[name="nome_solicitante"]
// dados.email === valor de input[name="email_solicitante"]Regra:
loadFieldsnão faz parte deIFormRepository— é um utilitário de compatibilidade para processos legados. Novos processos devem usarrepository.load(section)exclusivamente.
Módulo workflow/ — Configuração e controle de tela
Conjunto de utilitários para mapear as atividades BPM em estados de tela — barra de progresso, visibilidade de módulos e editabilidade — sem hardcode de números de sequência nos Presenters.
defineWorkflowConfig — Configuração tipada
Define módulos e atividades em uma declaração única. O tipo ModuleId é inferido automaticamente — nenhum type alias manual é necessário.
import { defineWorkflowConfig } from 'jsegd-bpm'
export const workflowConfig = defineWorkflowConfig({
modules: [
{
moduleId: 'solicitante',
label: 'Dados Iniciais',
icon: 'file-text',
order: 1,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'solicitante.html',
},
{
moduleId: 'juridico',
label: 'Jurídico',
icon: 'gavel',
order: 2,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'juridico.html',
},
{
moduleId: 'financeiro',
label: 'Financeiro',
icon: 'dollar-sign',
order: 3,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'financeiro.html',
},
],
activities: [
{
sequence: 9,
description: 'Solicitação inicial',
kind: 'human',
stage: 'solicitacao',
modules: { visible: ['solicitante'], editable: ['solicitante'], active: 'solicitante' },
},
{
sequence: 53,
description: 'Aprovação jurídica',
kind: 'human',
stage: 'juridico',
modules: { visible: ['solicitante', 'juridico'], editable: ['juridico'], active: 'juridico' },
},
{
sequence: 61,
description: 'Análise financeira',
kind: 'human',
stage: 'financeiro',
modules: { visible: ['solicitante', 'juridico', 'financeiro'], editable: ['financeiro'] },
},
{ sequence: 88, description: 'Roteamento interno', kind: 'routing' },
],
} as const)
// Tipo de ModuleId inferido: 'solicitante' | 'juridico' | 'financeiro'ActivityKind — controla o comportamento na barra de progresso:
| Valor | Comportamento |
| ----------- | ------------------------------------------------------------------- |
| 'human' | Ação de usuário — estágio atual destacado na barra |
| 'system' | Aguardando retorno externo (webhook) — exibe spinner |
| 'support' | Suporte transversal — sobrepõe banner de alerta; não avança a barra |
| 'routing' | Roteamento interno do motor BPM — invisível na barra |
extractCompletedStages / extractCurrentStage — Histórico do processo
Parseia o histórico bruto retornado por parent.ECM.workflowView.processDefinition.urlHistory e produz um ProcessHistory para consumo pelo ScreenContext.
import { extractCompletedStages, extractCurrentStage } from 'jsegd-bpm'
import type { BpmHistoryMap, ProcessHistory } from 'jsegd-bpm'
const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
const currentSequence: number = parent.ECM.workflowView.processDefinition.cardIndex
const historyType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
const completedStages = extractCompletedStages(rawHistory, workflowConfig.activities)
// Ex: ['solicitacao', 'juridico']
const currentStage = extractCurrentStage(currentSequence, historyType, workflowConfig.activities)
// Ex: { stage: 'financeiro', status: 'current' }
const processHistory: ProcessHistory = { completedStages, currentStage }resolveWorkflowScreen + ScreenContext — Estado da tela
Cruza a configuração com o histórico para produzir um ScreenContext — o único objeto que os Presenters precisam consultar.
import { resolveWorkflowScreen } from 'jsegd-bpm'
const screen = resolveWorkflowScreen(workflowConfig, currentSequence, processHistory)
// Visibilidade e editabilidade
screen.isVisible('juridico') // true
screen.isEditable('financeiro') // false
screen.isActive('juridico') // true
// Estado da atividade
screen.isAwaitingSystem() // true quando kind === 'system' ou status === 'waiting'
screen.isInSupport() // true para kind === 'support'
// Barra de progresso
const stages = screen.getProgressBarStages()
// [
// { stage: 'solicitacao', status: 'done' },
// { stage: 'juridico', status: 'done' },
// { stage: 'financeiro', status: 'current' },
// ]
// Módulos
const visiveis = screen.getVisibleModules() // ResolvedModule[] — apenas isVisible = true
const navegacao = screen.getNavigationModules() // ResolvedModule[] — apenas showInNavigation = true e visívelregisterStores — Alpine stores tipados
Registra múltiplos Alpine stores em uma única chamada e retorna um proxy tipado para acesso direto sem casting manual.
import Alpine from 'alpinejs'
import { registerStores } from 'jsegd-bpm'
type PerfilId = 'admin' | 'analista' | 'visualizador'
const stores = registerStores(Alpine, {
usuario: { nome: '', perfil: '' as PerfilId, matricula: '' },
ui: { carregando: false, erro: null as string | null },
})
Alpine.start()
// Acesso tipado — sem Alpine.store('usuario') as UsuarioStore
stores.usuario.nome // string
stores.usuario.perfil // PerfilId
stores.ui.carregando // boolean
registerStoresaceitaPick<Alpine, 'store'>— compatível com qualquer mock em testes unitários.
Uso típico em um formulário EGD BPM
import Alpine from 'alpinejs'
import {
AttachButton,
AttachmentsTable,
WorkflowViewPatcher,
BpmFormRepository,
extractCompletedStages,
extractCurrentStage,
resolveWorkflowScreen,
registerStores,
} from 'jsegd-bpm'
import type { BpmHistoryMap } from 'jsegd-bpm'
import { workflowConfig } from './workflow.config'
// Registrar Custom Elements
customElements.define('attachment-button', AttachButton)
customElements.define('attachments-table', AttachmentsTable)
// Alpine stores
const stores = registerStores(Alpine, {
solicitante: { nome: '', departamento: '' },
ui: { carregando: false },
})
// Histórico e tela
const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
const seq: number = parent.ECM.workflowView.processDefinition.cardIndex
const histType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
const processHistory = {
completedStages: extractCompletedStages(rawHistory, workflowConfig.activities),
currentStage: extractCurrentStage(seq, histType, workflowConfig.activities),
}
const screen = resolveWorkflowScreen(workflowConfig, seq, processHistory)
// Patcher
await WorkflowViewPatcher.init()
WorkflowViewPatcher.patchAll()
WorkflowViewPatcher.addBeforeValidate(async () => {
const repo = new BpmFormRepository()
const dados = repo.load<{ nome: string }>('solicitante')
if (!dados.nome) throw new Error('Informe o nome do solicitante')
})
Alpine.start()Licença
Proprietária — EGD Tecnologia. Uso restrito a projetos autorizados.
