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

jsegd-bpm

v0.1.24

Published

Biblioteca runtime para desenvolvimento EGD BPM — EGD Tecnologia

Readme

jsEGD BPM

Biblioteca TypeScript de runtime para desenvolvimento de formulários e workflows em um ambiente BPM genérico.
Fornece componentes de anexo, gerenciamento de workers para datasets, controle do ciclo de vida do fluxo e utilitários de formulário integrados ao DOM.

Dependências obrigatórias: jsegd ≥ 1.1.0 e alpinejs ≥ 3.0.0


Instalação

npm install jsegd-bpm jsegd alpinejs

Entradas 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 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://exemplo.local', {
    datasetId: 'dsRegistros',
    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 cache nem cacheOptions for 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="DOCUMENTO"></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="DOCUMENTO" 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 do formulário (data-group / data-helper).

import { BeforeSendValidate } from 'jsegd-bpm'
import type { FieldError } from 'jsegd-bpm'

function validarFormulario() {
    const erros: FieldError[] = []

    const campoTexto = document.querySelector<HTMLInputElement>('[name="nome"]')
    if (!campoTexto?.value) {
        erros.push({ field: campoTexto, errorMessage: 'Informe o nome' })
    }

    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 formulário ou em hooks de WorkflowViewPatcher.


Validator — Validação de campos e de submit

Validator.getInstance().register(component, rules) é a única chamada necessária no init() de um componente Alpine. Ela combina:

  • Validação reativa por campo$watch debounced por campo, atualizando errors automaticamente a cada mudança
  • Validação de submit — regras agrupadas pelos sequences do componente, executadas por execute(sequence) no momento do envio

As funções de validação devem ser arrow functions para capturar this do componente Alpine via escopo léxico. A avaliação usa curto-circuito: a primeira regra que falha retorna imediatamente com sua mensagem, sem executar as demais.

Tipos principais

| Tipo | Descrição | | --------------------- | ---------------------------------------------------------------------- | | ValidationRule | { fn: () => boolean \| Promise<boolean>; message: string } | | ValidationRules | Record<string, ValidationRule[]> — mapa de campo → array de regras | | FieldResult | { isValid: boolean; message: string } — resultado por campo | | BaseError | Record<string, FieldResult> — estado de validação de todos os campos | | BaseAlpineComponent | Interface base para componentes Alpine no escopo BPM |


register — Única chamada em init()

import { Validator } from 'jsegd-bpm'
import type { AlpineComponent } from 'alpinejs'
import type { ValidationRules, BaseAlpineComponent } from 'jsegd-bpm'

interface ContatoComponent extends BaseAlpineComponent {
    telefone: string
    email: string
}

export function contatoComponent({ key, sequences }: { key: string; sequences: number[] }): AlpineComponent<ContatoComponent> {
    return {
        key,
        sequences,
        telefone: '',
        email: '',
        errors: {},
        init() {
            Validator.getInstance().register(this, {
                telefone: [
                    { fn: () => this.telefone !== '', message: 'Telefone obrigatório' },
                    {
                        fn: () => /^\(?([1-9]{2})\)? ?(2|3|4|5|7|8)\d{3}[- ]?\d{4}$/.test(this.telefone),
                        message: 'Formato de telefone inválido',
                    },
                ],
                email: [
                    {
                        fn: () => this.email === '' || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email),
                        message: 'E-mail inválido',
                    },
                ],
            })
            // ↑ inicializa errors por campo, registra $watch debounced e cadastra regras de submit
        },
    }
}

O terceiro argumento opcional debounceMs (padrão: 300) controla o tempo de espera antes de disparar a validação após cada mudança. register retorna uma função de cancelamento que remove as regras de submit — útil ao desmontar o componente.

Exemplo no HTML

<div x-data="contatoComponent({ key: '<%= id %>', sequences: [0] })">
    <div class="form-group" :class="{ 'has-error': !errors.telefone?.isValid }">
        <label for="<%= id %>_telefone">Telefone</label>
        <input type="tel" id="<%= id %>_telefone" name="<%= id %>_telefone" x-model="telefone" />
        <p class="help-block" x-show="!errors.telefone?.isValid" x-text="errors.telefone?.message"></p>
    </div>

    <div class="form-group" :class="{ 'has-error': !errors.email?.isValid }">
        <label for="<%= id %>_email">E-mail</label>
        <input type="text" id="<%= id %>_email" name="<%= id %>_email" x-model="email" />
        <p class="help-block" x-show="!errors.email?.isValid" x-text="errors.email?.message"></p>
    </div>
</div>

x-model é suficiente. O $watch registrado por register já detecta mudanças reativas — não é necessário @input separado. O ?. (optional chaining) protege o acesso antes de init() inicializar o campo em errors.


execute — Validação de submit

execute(sequence) executa todas as regras registradas para aquele sequence. Rejeita a Promise se qualquer regra falhar — use dentro de WorkflowViewPatcher.addBeforeValidate para bloquear o envio do formulário.

WorkflowViewPatcher.addBeforeValidate(async () => {
    await Validator.getInstance().execute(0) // um dos sequences do componente
})

unregister(component, rules) remove um conjunto específico por referência. clear(sequences?) remove todas as regras dos sequences informados ou limpa tudo se chamado sem argumento.


WorkflowViewPatcher — Hooks no widget de processo

Substitui métodos do widget nativo ECM_WKFView para interceptar o ciclo de envio do fluxo sem modificar o formulário.

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_principal"]')?.value
    if (!valor) throw new Error('Campo principal 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 próxima etapa', href: '/proxima-etapa' })
})

// 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 | | --------------------- | -------------- | ------------------ | | departamento_indice | departamento | indice | | area_cep | area | cep | | vigencia_prazo | vigencia | prazo | | cadastro_cep | cadastro | cep |

Importante: o {domínio} é o prefixo persistido no banco — é parte integrante do nome da coluna e não deve ser omitido no HTML. O load e o save gerenciam 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 Departamento {
    indice: string
    primeiroPagamento: string
    reajuste: string
}

interface Area {
    cep: string
    logradouro: string
    municipio: string
}

interface ItemTabela {
    descricao: string
    valor: string
}

const repo = new BpmFormRepository()

// Ler domínio 'departamento' — lê campos com name="departamento_*" do DOM
// DOM: departamento_indice, departamento_primeiro_pagamento, departamento_reajuste
const departamento = repo.load<Departamento>('departamento')
console.log(departamento.indice) // valor de input[name="departamento_indice"]
console.log(departamento.primeiroPagamento) // valor de input[name="departamento_primeiro_pagamento"]

// Ler domínio 'area' — desambigua de 'cadastro_cep', 'departamento_cep', etc.
const area = repo.load<Area>('area')
console.log(area.cep) // valor de input[name="area_cep"]

// Ler tabela filha
const itens = repo.loadTable<ItemTabela>('tabItens')

// Persistir no DOM — save converte camelCase → prefixo+snake_case
repo.save<Departamento>('departamento', { indice: 'IGPM', primeiroPagamento: '2026-02-01' })
// escreve: input[name="departamento_indice"] e input[name="departamento_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 dados

Processos 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 Requisitante {
    nome: string
    email: string
}

// Campo legado: sufixo em vez de prefixo — nome_requisitante, email_requisitante
const dados = loadFields<Requisitante>({
    nome: 'nome_requisitante',
    email: 'email_requisitante',
})
// dados.nome === valor de input[name="nome_requisitante"]
// dados.email === valor de input[name="email_requisitante"]

Regra: loadFields não faz parte de IFormRepository — é um utilitário de compatibilidade para processos legados. Novos processos devem usar repository.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: 'areaInicial',
            label: 'Dados Iniciais',
            icon: 'file-text',
            order: 1,
            showInNavigation: true,
            renderContent: true,
            contentIncludePath: 'area-inicial.html',
        },
        {
            moduleId: 'areaA',
            label: 'Área A',
            icon: 'gavel',
            order: 2,
            showInNavigation: true,
            renderContent: true,
            contentIncludePath: 'area-a.html',
        },
        {
            moduleId: 'areaB',
            label: 'Área B',
            icon: 'dollar-sign',
            order: 3,
            showInNavigation: true,
            renderContent: true,
            contentIncludePath: 'area-b.html',
        },
    ],
    activities: [
        {
            sequence: 9,
            description: 'Etapa inicial',
            kind: 'human',
            stage: 'inicio',
            modules: { visible: ['areaInicial'], editable: ['areaInicial'], active: 'areaInicial' },
        },
        {
            sequence: 53,
            description: 'Análise da área A',
            kind: 'human',
            stage: 'analiseA',
            modules: { visible: ['areaInicial', 'areaA'], editable: ['areaA'], active: 'areaA' },
        },
        {
            sequence: 61,
            description: 'Análise da área B',
            kind: 'human',
            stage: 'analiseB',
            modules: { visible: ['areaInicial', 'areaA', 'areaB'], editable: ['areaB'] },
        },
        { sequence: 88, description: 'Roteamento interno', kind: 'routing' },
    ],
} as const)
// Tipo de ModuleId inferido: 'areaInicial' | 'areaA' | 'areaB'

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: ['inicio', 'analiseA']

const currentStage = extractCurrentStage(currentSequence, historyType, workflowConfig.activities)
// Ex: { stage: 'analiseB', 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('areaA') // true
screen.isEditable('areaB') // false
screen.isActive('areaA') // 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: 'inicio',   status: 'done' },
//   { stage: 'analiseA',  status: 'done' },
//   { stage: 'analiseB',  status: 'current' },
// ]

// Módulos
const visiveis = screen.getVisibleModules() // ResolvedModule[] — apenas isVisible = true
const navegacao = screen.getNavigationModules() // ResolvedModule[] — apenas showInNavigation = true e visível

registerStores — 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

registerStores aceita Pick<Alpine, 'store'> — compatível com qualquer mock em testes unitários.


createMenuComponent / JsegdMenu — Menu Alpine por sequência do processo

Disponibiliza um componente Alpine customizado para renderizar menu de navegação por etapas, com exibição e ativação dos itens baseada na workflow sequence atual lida do ECM.

Contratos

import type { MenuItem } from 'jsegd-bpm'

const ETAPAS = {
    DISPLAY_ALWAYS: 0,
    INICIO: 9,
    ANALISE: 17,
    APROVACAO: 23,
} as const

const menuConfig: MenuItem[] = [
    {
        id: 'dados-iniciais',
        label: 'Dados iniciais',
        icon: 'fluigicon fluigicon-file-text',
        displayOn: [ETAPAS.DISPLAY_ALWAYS],
        activateOn: [ETAPAS.INICIO],
    },
    {
        id: 'analise',
        label: 'Análise',
        icon: 'fluigicon fluigicon-search',
        displayOn: [ETAPAS.ANALISE, ETAPAS.APROVACAO],
        activateOn: [ETAPAS.ANALISE],
    },
    {
        id: 'aprovacao',
        label: 'Aprovação',
        icon: 'fluigicon fluigicon-check-circle',
        displayOn: [ETAPAS.APROVACAO],
        activateOn: [ETAPAS.APROVACAO],
    },
]

Registro Alpine

import Alpine from 'alpinejs'
import { createMenuComponent } from 'jsegd-bpm'

Alpine.data('jsegdMenu', createMenuComponent(menuConfig))
Alpine.start()

Uso no HTML

<bpm-menu logo="/assets/logo.svg" theme="light" data-name="jsegdMenu"></bpm-menu>

API pública exportada

import { createMenuComponent, JsegdMenu } from 'jsegd-bpm'
import type { MenuItem, BaseMenuComponent } from 'jsegd-bpm'

Notas de uso:

  • displayOn: define em quais sequences o item aparece.
  • activateOn: define em quais sequences o item fica ativo.
  • 0 funciona como curinga (DISPLAY_ALWAYS) para exibir/ativar em qualquer etapa.
  • A sequence utilizada pelo menu é obtida do ECM em tempo de execução.

createAutocompleteComponent / BpmAutocomplete — Autocomplete Alpine por dataset

Custom element <bpm-autocomplete> para busca reativa em datasets do Fluig. Substitui os widgets legados jQuery zoom e autocomplete, utilizando exclusivamente as classes CSS do Fluig Style Guide disponíveis globalmente no ambiente.

Atributos

| Atributo | Tipo | Obrigatório | Padrão | Descrição | | --------------- | --------- | ----------- | --------------------- | ----------------------------------------------------------------------------------- | | dataset-id | string | ✅ | — | Identificador do dataset a consultar | | key-field | string | — | "CODIGO" | Campo de chave única do registro | | display-field | string | — | "DESCRICAO" | Campo exibido como rótulo no dropdown | | label | string | — | "Search" | Texto do <label> do campo | | placeholder | string | — | "Type to search..." | Placeholder do input | | data-name | string | — | "bpmAutocomplete" | Prefixo do nome do Alpine.data; sufixo único adicionado por instância | | initial-value | string | — | — | Valor de chave inicial; o componente hidrata o rótulo automaticamente via API | | filters | string | — | "[]" | JSON de AutocompleteFilter[] — filtros estáticos aplicados a toda busca | | limit | integer | — | 20 | Número máximo de registros retornados por consulta (parâmetro limit da API Fluig) |

Eventos (despachados no próprio elemento, com bubbles: true)

| Evento | detail | Quando | | --------------------------- | ---------------------- | ---------------------------- | | jsegd:autocomplete:load | { count: number } | Resultados recebidos da API | | jsegd:autocomplete:select | { item, key, label } | Usuário selecionou um item | | jsegd:autocomplete:change | { item, key, label } | Disparado junto com select | | jsegd:autocomplete:clear | void | Seleção foi limpa | | jsegd:autocomplete:error | { message: string } | Requisição falhou |

Filtros (AutocompleteFilter)

Cada item do array filters é um objeto com os seguintes campos:

| Campo | Tipo | Padrão | Descrição | | ------------ | ---------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- | | field | string | — | Nome do campo no dataset (constraintsField na API) | | value | string | — | Valor a comparar (constraintsInitialValue = constraintsFinalValue) | | operator | 'MUST' \| 'SHOULD' \| 'MUST_NOT' | 'MUST' | Tipo de constraint (constraintsType na API) | | likeSearch | boolean | false | Quando true, compara parcialmente (constraintsLikeSearch na API); o símbolo % pode ser usado em value |

import type { AutocompleteFilter } from 'jsegd-bpm'

// Igualdade exata: STATUS = 'ATIVO'
const filtroStatus: AutocompleteFilter = { field: 'STATUS', value: 'ATIVO', operator: 'MUST' }

// Busca parcial: NOME contém 'Silva'
const filtroNome: AutocompleteFilter = { field: 'NOME', value: 'Silva', operator: 'SHOULD', likeSearch: true }

// Exclusão: TIPO diferente de 'INATIVO'
const filtroExclusao: AutocompleteFilter = { field: 'TIPO', value: 'INATIVO', operator: 'MUST_NOT' }

Uso no HTML

<!-- Uso básico -->
<bpm-autocomplete dataset-id="dsRegistros" key-field="CODIGO" display-field="DESCRICAO" label="Registro"></bpm-autocomplete>

<!-- Com valor inicial (dado já salvo no banco — formulário de edição) -->
<bpm-autocomplete dataset-id="dsRegistros" key-field="CODIGO" display-field="DESCRICAO" label="Registro" initial-value="ABC123"></bpm-autocomplete>

<!-- Com filtros estáticos e limite personalizado -->
<bpm-autocomplete
    dataset-id="dsRegistros"
    key-field="CODIGO"
    display-field="DESCRICAO"
    limit="10"
    filters='[
        {"field":"STATUS","value":"ATIVO","operator":"MUST"},
        {"field":"DESCRICAO","value":"cliente","operator":"SHOULD","likeSearch":true}
    ]'
></bpm-autocomplete>

<!-- Ouvindo eventos via listener direto -->
<script>
    document.querySelector('bpm-autocomplete').addEventListener('jsegd:autocomplete:select', (e) => {
        console.log('Selecionado:', e.detail.item) // objeto completo da API
        console.log('Chave:', e.detail.key) // e.detail.item[keyField]
        console.log('Rótulo:', e.detail.label) // e.detail.item[displayField]
    })
</script>

<!-- Integração Alpine: evento na tag + estado local -->
<div x-data="{ fornecedor: null }">
    <bpm-autocomplete
        dataset-id="dsFornecedores"
        key-field="CODIGO"
        display-field="RAZAO_SOCIAL"
        label="Fornecedor"
        @jsegd:autocomplete:select="fornecedor = $event.detail.item"
        @jsegd:autocomplete:clear="fornecedor = null"
    ></bpm-autocomplete>

    <p x-show="fornecedor" x-text="'CNPJ: ' + fornecedor?.CNPJ"></p>
</div>

Filtros dinâmicos via setFilters

Quando o valor de outro campo condiciona os resultados, obtenha a referência ao componente Alpine via _x_dataStack e chame setFilters:

<div x-data="meuFormulario()">
    <bpm-autocomplete id="ac-produto" dataset-id="dsProdutos" display-field="DESCRICAO"></bpm-autocomplete>
</div>

<script>
    // Após Alpine inicializar:
    const el = document.querySelector('#ac-produto')

    // Filtro estático pelo atributo (JSON) — preferível para filtros fixos:
    // <bpm-autocomplete filters='[{"field":"CATEGORIA","value":"ELETRONICO","operator":"MUST"}]'>

    // Filtro dinâmico via API JavaScript — para filtros que dependem de estado em runtime:
    function atualizarFiltros(categoria) {
        const component = el.querySelector('[x-data]')?._x_dataStack?.[0]
        if (component) {
            component.setFilters([
                { field: 'CATEGORIA', value: categoria, operator: 'MUST' },
                { field: 'DESCRICAO', value: '%' + categoria, operator: 'SHOULD', likeSearch: true },
            ])
        }
    }
</script>

API pública exportada

import { createAutocompleteComponent, BpmAutocomplete, FILTER_OPERATOR_MAP } from 'jsegd-bpm'
import type {
    AutocompleteConfig,
    AutocompleteFilter,
    AutocompleteFilterOperator,
    AutocompleteItem,
    BaseAutocompleteComponent,
    AutocompleteSelectDetail,
    AutocompleteErrorDetail,
    AutocompleteLoadDetail,
} from 'jsegd-bpm'

Notas de uso:

  • O BpmAutocomplete registra-se automaticamente como <bpm-autocomplete> ao importar o módulo; não é necessário chamar customElements.define.
  • Cada instância na página tem um Alpine.data com nome único, portanto múltiplos componentes coexistem sem compartilhar estado.
  • A busca usa GET /dataset/api/v2/dataset-handle/search?datasetId=...&constraintsField=... com debounce de 300 ms, mínimo de 2 caracteres e cancelamento de requisição anterior via AbortController.
  • Para suporte a valores salvos em banco (edição de formulário), use o atributo initial-value — o componente realiza automaticamente a busca por chave (constraintsType=MUST) para montar a exibição.
  • O operador likeSearch: true em um filtro mapeia para constraintsLikeSearch=true na API; o símbolo % pode ser usado em value como curinga.
  • O atributo limit (padrão: 20) controla o parâmetro limit da API; valores menores melhoram a performance do dropdown.

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, {
    departamento: { nome: '', area: '' },
    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 }>('departamento')
    if (!dados.nome) throw new Error('Informe o nome')
})

Alpine.start()

Licença

Proprietária — uso restrito a projetos autorizados.