235 lines
5.7 KiB
TypeScript
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"] } } },
|
|
],
|
|
},
|
|
],
|
|
})
|