remoção do filtro 26 por filtro delta
This commit is contained in:
parent
98a531dbd3
commit
b0b76562b7
12 changed files with 348 additions and 542 deletions
162
src/filtroDelta.ts
Normal file
162
src/filtroDelta.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import z from "zod"
|
||||
|
||||
/**
|
||||
* Operadores permitidos para comparação.
|
||||
*/
|
||||
export const zOperadorFiltroDelta = z.enum([">", "<", ">=", "<=", "=", "!=", "in", "like"])
|
||||
|
||||
/**
|
||||
* 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: string) => {
|
||||
/**
|
||||
* 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 type tipoOperadorFiltroDelta = z.infer<typeof zOperadorFiltroDelta>
|
||||
export type tipoColunaFiltroDelta = z.infer<typeof zColunaFiltroDelta>
|
||||
export type tipoCondicaoFiltroDelta = z.infer<typeof zCondicaoFiltroDelta>
|
||||
export type tipoFiltroDelta = z.infer<typeof zFiltroDelta>
|
||||
|
|
@ -3,10 +3,10 @@ export * from "./auditoria"
|
|||
export * from "./cacheMemoria"
|
||||
export * from "./constantes"
|
||||
export * from "./consulta"
|
||||
export * from "./dayjs26"
|
||||
export * from "./ecosistema"
|
||||
export * from "./extensoes"
|
||||
export * from "./extensoes"
|
||||
export * from "./filtroDelta"
|
||||
export * from "./local"
|
||||
export * from "./postgres"
|
||||
export * from "./produtos"
|
||||
|
|
@ -15,7 +15,7 @@ export * from "./testes-de-variaveis"
|
|||
export * from "./texto_busca"
|
||||
export * from "./tipagemRotas"
|
||||
export * from "./tipagemRotas"
|
||||
export * from "./tipoFiltro.26"
|
||||
|
||||
export * from "./unidades_medida"
|
||||
export * from "./uuid"
|
||||
export * from "./variaveisComuns"
|
||||
|
|
|
|||
|
|
@ -1,233 +0,0 @@
|
|||
import { z } from "zod"
|
||||
|
||||
/**
|
||||
* =============================================================================
|
||||
* tipoFiltro26<T>
|
||||
* =============================================================================
|
||||
*
|
||||
* OBJETIVO
|
||||
* -----------------------------------------------------------------------------
|
||||
* Gerar automaticamente a tipagem de filtros compatíveis com operadores
|
||||
* padrão do PostgreSQL, a partir de um tipo base T.
|
||||
*
|
||||
* Este tipo foi projetado para:
|
||||
* - Construção de filtros dinâmicos
|
||||
* - Geração posterior de WHERE (Knex / SQL)
|
||||
* - Uso seguro por IA (evita filtros inválidos em nível de tipo)
|
||||
*
|
||||
*
|
||||
* FORMATO DO FILTRO
|
||||
* -----------------------------------------------------------------------------
|
||||
* 1) Campos simples:
|
||||
*
|
||||
* {
|
||||
* idade: { ">=": 18 }
|
||||
* }
|
||||
*
|
||||
* 2) Campos aninhados:
|
||||
*
|
||||
* {
|
||||
* carro: {
|
||||
* ano: { "=": 2020 }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 3) Operador E (AND):
|
||||
*
|
||||
* {
|
||||
* E: [
|
||||
* { idade: { ">=": 18 } },
|
||||
* { nome: { like: "%pa%" } }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* 4) Operador OU (OR):
|
||||
*
|
||||
* {
|
||||
* OU: [
|
||||
* { idade: { "<": 18 } },
|
||||
* { idade: { ">=": 60 } }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* 5) Combinação complexa:
|
||||
*
|
||||
* {
|
||||
* idade: { ">=": 18 },
|
||||
* OU: [
|
||||
* { nome: { like: "%pa%" } },
|
||||
* {
|
||||
* E: [
|
||||
* { carro: { ano: { "=": 2020 } } },
|
||||
* { carro: { modelo: { in: ["Civic"] } } }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* REGRAS IMPORTANTES (PARA IA)
|
||||
* -----------------------------------------------------------------------------
|
||||
* - Apenas campos existentes em T podem ser usados.
|
||||
* - Operadores são restritos por tipo do campo.
|
||||
* - Objetos são tratados recursivamente.
|
||||
* - Arrays NÃO são tratados como objeto recursivo.
|
||||
* - Funções NÃO são consideradas campos filtráveis.
|
||||
*
|
||||
*
|
||||
* OPERADORES SUPORTADOS
|
||||
* -----------------------------------------------------------------------------
|
||||
* number:
|
||||
* =, !=, >, >=, <, <=, in
|
||||
*
|
||||
* string:
|
||||
* =, !=, like, in
|
||||
*
|
||||
* boolean:
|
||||
* =, !=, in
|
||||
*
|
||||
* Não há suporte automático a:
|
||||
* - null
|
||||
* - date
|
||||
* - jsonb
|
||||
* - arrays
|
||||
*
|
||||
* Essas extensões devem ser adicionadas explicitamente.
|
||||
*
|
||||
* =============================================================================
|
||||
*/
|
||||
|
||||
/* =============================================================================
|
||||
OPERADORES POSTGRESQL POR TIPO
|
||||
============================================================================= */
|
||||
|
||||
export enum operadores26 {
|
||||
"=" = "=",
|
||||
"!=" = "!=",
|
||||
">" = ">",
|
||||
">=" = ">=",
|
||||
"<" = "<",
|
||||
"<=" = "<=",
|
||||
like = "like",
|
||||
in = "in",
|
||||
}
|
||||
|
||||
export enum agrupadores26 {
|
||||
E = "E",
|
||||
OU = "OU",
|
||||
}
|
||||
|
||||
type PgOpsNumber = {
|
||||
[K in Extract<operadores26, "=" | "!=" | ">" | ">=" | "<" | "<=">]?: number
|
||||
} & {
|
||||
[K in Extract<operadores26, "in">]?: number[]
|
||||
}
|
||||
|
||||
type PgOpsString = {
|
||||
[K in Extract<operadores26, "=" | "!=" | "like">]?: string
|
||||
} & {
|
||||
[K in Extract<operadores26, "in">]?: string[]
|
||||
}
|
||||
|
||||
type PgOpsBoolean = {
|
||||
[K in Extract<operadores26, "=" | "!=">]?: boolean
|
||||
} & {
|
||||
[K in Extract<operadores26, "in">]?: boolean[]
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
SELEÇÃO AUTOMÁTICA DE OPERADORES BASEADA NO TIPO DO CAMPO
|
||||
============================================================================= */
|
||||
|
||||
type PgOpsFor<V> = V extends number
|
||||
? PgOpsNumber
|
||||
: V extends string
|
||||
? PgOpsString
|
||||
: V extends boolean
|
||||
? PgOpsBoolean
|
||||
: never
|
||||
|
||||
/* =============================================================================
|
||||
UTILITÁRIO: DETECTAR OBJETO PLANO
|
||||
============================================================================= */
|
||||
|
||||
type IsPlainObject<T> = T extends object
|
||||
? T extends Function
|
||||
? false
|
||||
: T extends readonly any[]
|
||||
? false
|
||||
: true
|
||||
: false
|
||||
|
||||
/* =============================================================================
|
||||
FILTRO RECURSIVO POR CAMPOS
|
||||
============================================================================= */
|
||||
|
||||
type FiltroCampos<T> = {
|
||||
[K in keyof T]?: IsPlainObject<T[K]> extends true ? tipoFiltro26<T[K]> : PgOpsFor<T[K]>
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
TIPO PRINCIPAL EXPORTADO
|
||||
============================================================================= */
|
||||
|
||||
export type tipoFiltro26<T> = FiltroCampos<T> & {
|
||||
/**
|
||||
* E => AND lógico
|
||||
* Todos os filtros dentro do array devem ser verdadeiros.
|
||||
*/
|
||||
E?: tipoFiltro26<T>[]
|
||||
|
||||
/**
|
||||
* OU => OR lógico
|
||||
* Pelo menos um filtro dentro do array deve ser verdadeiro.
|
||||
*/
|
||||
OU?: tipoFiltro26<T>[]
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
VALIDAÇÃO ESTRUTURAL (ZOD)
|
||||
============================================================================= */
|
||||
|
||||
const zOperadores = z.nativeEnum(operadores26)
|
||||
const zValor = z.any()
|
||||
const zCondicao = z.record(zOperadores, zValor)
|
||||
|
||||
export const zFiltro26: z.ZodType<any> = z.lazy(() =>
|
||||
z
|
||||
.object({
|
||||
E: z.array(zFiltro26).optional(),
|
||||
OU: z.array(zFiltro26).optional(),
|
||||
})
|
||||
.catchall(z.union([zCondicao, zFiltro26])),
|
||||
)
|
||||
|
||||
/* =============================================================================
|
||||
EXEMPLO DE USO
|
||||
============================================================================= */
|
||||
|
||||
type Pessoa = {
|
||||
codigo: string
|
||||
nome: string
|
||||
idade: number
|
||||
carro: {
|
||||
modelo: string
|
||||
ano: number
|
||||
}
|
||||
}
|
||||
|
||||
export const criarFiltro26 = <T>(filtro: tipoFiltro26<T>) => filtro
|
||||
|
||||
const _filtro = criarFiltro26<Pessoa>({
|
||||
idade: { [operadores26[">="]]: 18 },
|
||||
|
||||
[agrupadores26.OU]: [
|
||||
{ nome: { [operadores26.like]: "%pa%" } },
|
||||
{
|
||||
[agrupadores26.E]: [
|
||||
{ carro: { ano: { [operadores26["="]]: 2020 } } },
|
||||
{ carro: { modelo: { [operadores26.in]: ["Civic", "Corolla"] } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue