implementado typescript 7
This commit is contained in:
parent
cd4ea4d36b
commit
e9a323f18c
80 changed files with 5711 additions and 2455 deletions
238
dist-front/filtroDelta.js
Normal file
238
dist-front/filtroDelta.js
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import z from "zod";
|
||||
/**
|
||||
* =============================================================================
|
||||
* FILTRO DELTA (zFiltroDelta)
|
||||
* =============================================================================
|
||||
*
|
||||
* OBJETIVO
|
||||
* -----------------------------------------------------------------------------
|
||||
* Novo sistema de filtros projetado para maior flexibilidade e geração segura
|
||||
* de instruções SQL dinâmicas (WHERE).
|
||||
*
|
||||
* REGRAS IMPORTANTES (PARA IA)
|
||||
* -----------------------------------------------------------------------------
|
||||
* - O filtro é composto de duas partes obrigatórias: `condicoes` e `formula`.
|
||||
* - Apenas referências aos `id`s das `condicoes` podem ser usadas na `formula`.
|
||||
* - Não use sintaxe baseada em objetos aninhados para agrupamento (ex: { E: [], OU: [] }). Use fórmulas em string.
|
||||
*
|
||||
* 1) CONDIÇÕES (`condicoes`):
|
||||
* Array de objetos contendo as definições isoladas de cada comparação.
|
||||
* Exemplo:
|
||||
* [
|
||||
* { id: "{1}", coluna: "idade", operador: ">=", valor: 18 },
|
||||
* { id: "{2}", coluna: "nome", operador: "like", valor: "%pa%" }
|
||||
* ]
|
||||
* - `id`: Deve SEMPRE seguir o formato {identificador}. Ex: `{c1}`, `{idade_min}`.
|
||||
* - `coluna`: O nome da coluna ou um path JSONB válido (ex: `veiculo.ano`).
|
||||
* - `operador`: Um dos valores do tipo `tipoOperadorFiltroDelta` (veja a constante).
|
||||
*
|
||||
* 2) FÓRMULA (`formula`):
|
||||
* String que define a expressão lógica relacionando as condições através de seus `id`s.
|
||||
* Operadores Lógicos Suportados (EM MAIÚSCULO):
|
||||
* - `E` (AND)
|
||||
* - `OU` (OR)
|
||||
* - `NAO` (NOT)
|
||||
* Suporta o agrupamento lógico usando parênteses `()`
|
||||
* Exemplo válido: "({1} E NAO({2})) OU {3}"
|
||||
*
|
||||
* =============================================================================
|
||||
*/
|
||||
/**
|
||||
* Operadores permitidos para comparação.
|
||||
*/
|
||||
export const zOperadorFiltroDelta = z.enum([">", "<", ">=", "<=", "=", "!=", "in", "like", "∩"]);
|
||||
export const operadorFiltroDeltaDetalhes = {
|
||||
">": {
|
||||
rotulo: "Maior que",
|
||||
descricao: "O valor de A deve ser maior que o valor de B",
|
||||
},
|
||||
"<": {
|
||||
rotulo: "Menor que",
|
||||
descricao: "O valor de A deve ser menor que o valor de B",
|
||||
},
|
||||
">=": {
|
||||
rotulo: "Maior ou igual a",
|
||||
descricao: "O valor de A deve ser maior ou igual ao valor de B",
|
||||
},
|
||||
"<=": {
|
||||
rotulo: "Menor ou igual a",
|
||||
descricao: "O valor de A deve ser menor ou igual ao valor de B",
|
||||
},
|
||||
"=": {
|
||||
rotulo: "Igual a",
|
||||
descricao: "O valor de A deve ser igual ao valor de B",
|
||||
},
|
||||
"!=": {
|
||||
rotulo: "Diferente de",
|
||||
descricao: "O valor de A deve ser diferente do valor de B",
|
||||
},
|
||||
in: {
|
||||
rotulo: "Está em",
|
||||
descricao: "O valor de A deve estar presente na coleção ou lista definida em B",
|
||||
},
|
||||
like: {
|
||||
rotulo: "Contém",
|
||||
descricao: "O valor de A deve conter o texto informado em B",
|
||||
},
|
||||
"∩": {
|
||||
rotulo: "Interseção",
|
||||
descricao: "A e B devem possuir pelo menos um elemento em comum",
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Valida o identificador da condição.
|
||||
*
|
||||
* Formato esperado:
|
||||
* {identificador} ou {identificador:sufixo}
|
||||
*/
|
||||
const regexIdCondicaoFiltroDelta = /^\{[\w:]+\}$/;
|
||||
/**
|
||||
* Valida nomes de colunas e caminhos jsonb.
|
||||
*/
|
||||
const regexColunaFiltroDelta = /^\w+(\.\w+)*$/;
|
||||
/**
|
||||
* Coluna simples ou caminho aninhado de jsonb.
|
||||
*/
|
||||
export const zColunaFiltroDelta = z
|
||||
.string()
|
||||
.regex(regexColunaFiltroDelta, "Coluna inválida")
|
||||
.describe("nome da coluna ou caminho aninhado do jsonb");
|
||||
/**
|
||||
* Estrutura individual de uma condição do filtro.
|
||||
*/
|
||||
export const zCondicaoFiltroDelta = z.object({
|
||||
/**
|
||||
* Identificador único da condição.
|
||||
*
|
||||
* Exemplo:
|
||||
* {1}
|
||||
*/
|
||||
id: z
|
||||
.string()
|
||||
.describe("id da condição do filtro")
|
||||
.regex(regexIdCondicaoFiltroDelta, "O id deve seguir o formato {identificador}"),
|
||||
/**
|
||||
* Nome da coluna ou caminho jsonb.
|
||||
*/
|
||||
coluna: zColunaFiltroDelta,
|
||||
/**
|
||||
* Operador da comparação.
|
||||
*/
|
||||
operador: zOperadorFiltroDelta,
|
||||
/**
|
||||
* Valor utilizado na comparação.
|
||||
*/
|
||||
valor: z.any().describe("valor de comparação"),
|
||||
});
|
||||
/**
|
||||
* Verifica se a fórmula possui uma estrutura lógica válida.
|
||||
*/
|
||||
const formulaFiltroDeltaValida = (formula) => {
|
||||
/**
|
||||
* Extrai apenas os tokens permitidos:
|
||||
* - referências de condição: {id} ou {id:sufixo}
|
||||
* - operadores lógicos: E, OU
|
||||
* - negação: NAO(...)
|
||||
* - parênteses
|
||||
*/
|
||||
const tokensPermitidos = formula.match(/\{[\w:]+\}|\bNAO\b|\bE\b|\bOU\b|\(|\)/g) || [];
|
||||
/**
|
||||
* Reconstrói a fórmula usando apenas os tokens válidos.
|
||||
*/
|
||||
const formulaReconstruida = tokensPermitidos.join("");
|
||||
/**
|
||||
* Remove espaços para comparar o texto original com o texto reconstruído.
|
||||
*/
|
||||
const formulaOriginalSemEspacos = formula.replace(/\s+/g, "");
|
||||
const formulaReconstruidaSemEspacos = formulaReconstruida.replace(/\s+/g, "");
|
||||
/**
|
||||
* Se forem diferentes, há conteúdo não permitido na fórmula.
|
||||
*/
|
||||
if (formulaOriginalSemEspacos !== formulaReconstruidaSemEspacos) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Converte a fórmula para uma expressão JavaScript equivalente.
|
||||
*
|
||||
* NAO(x) → !x
|
||||
* E → &&
|
||||
* OU → ||
|
||||
*/
|
||||
const expressaoLogica = formula
|
||||
.replace(/\{[\w:]+\}/g, "true")
|
||||
.replace(/\bNAO\b/g, "!")
|
||||
.replace(/\bE\b/g, "&&")
|
||||
.replace(/\bOU\b/g, "||");
|
||||
try {
|
||||
/**
|
||||
* Valida a ordem dos operadores, parênteses e estrutura lógica geral.
|
||||
*/
|
||||
new Function(`return (${expressaoLogica})`)();
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Estrutura principal do filtro delta.
|
||||
*/
|
||||
export const zFiltroDelta = z
|
||||
.object({
|
||||
/**
|
||||
* Lista de condições disponíveis.
|
||||
*/
|
||||
condicoes: z.array(zCondicaoFiltroDelta),
|
||||
/**
|
||||
* Fórmula lógica que combina as condições.
|
||||
*/
|
||||
formula: z.string().refine(formulaFiltroDeltaValida, {
|
||||
message: "Fórmula inválida. Use apenas referências de condições {id} ou {id:sufixo}, operadores E/OU/NAO e parênteses.",
|
||||
}),
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
/**
|
||||
* IDs das condições cadastradas.
|
||||
*/
|
||||
const idsCondicoes = new Set(value.condicoes.map((condicao) => condicao.id));
|
||||
/**
|
||||
* Referências usadas dentro da fórmula.
|
||||
*/
|
||||
const referenciasFormula = value.formula.match(/\{[\w:]+\}/g) || [];
|
||||
/**
|
||||
* Verifica se toda referência da fórmula existe em condicoes.
|
||||
*/
|
||||
for (const referencia of referenciasFormula) {
|
||||
if (!idsCondicoes.has(referencia)) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["formula"],
|
||||
message: `A referência ${referencia} não existe em condicoes.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export const criarFiltroDelta = (filtro) => zFiltroDelta.parse(filtro);
|
||||
/**
|
||||
* Fábrica para criar filtros com colunas estritamente tipadas.
|
||||
* Útil para autocompletar os nomes das colunas e garantir a estrutura no TypeScript.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { criarFiltroDelta } = iniciarFiltroDelta<"nome" | "idade" | "jsonb.propriedade">()
|
||||
*
|
||||
* const filtro = criarFiltroDelta({
|
||||
* condicoes: [
|
||||
* { id: "{c1}", coluna: "idade", operador: ">", valor: 18 },
|
||||
* { id: "{c2}", coluna: "nome", operador: "=", valor: "luiz" },
|
||||
* { id: "{c3}", coluna: "jsonb.propriedade", operador: "=", valor: "valor" },
|
||||
* ],
|
||||
* formula: "({c1} E {c2}) OU {c3}",
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const iniciarFiltroDelta = () => {
|
||||
return {
|
||||
criarFiltroDelta: criarFiltroDelta,
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue