_comuns/src/tipoFiltro.26.ts
2026-02-17 11:18:39 -03:00

235 lines
5.7 KiB
TypeScript

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"] } } },
],
},
],
})