import { z } from "zod" /** * ============================================================================= * tipoFiltro26 * ============================================================================= * * 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" | ">=" | "<" | "<=">]?: number } & { [K in Extract]?: number[] } type PgOpsString = { [K in Extract]?: string } & { [K in Extract]?: string[] } type PgOpsBoolean = { [K in Extract]?: boolean[] } /* ============================================================================= SELEÇÃO AUTOMÁTICA DE OPERADORES BASEADA NO TIPO DO CAMPO ============================================================================= */ type PgOpsFor = V extends number ? PgOpsNumber : V extends string ? PgOpsString : V extends boolean ? PgOpsBoolean : never /* ============================================================================= UTILITÁRIO: DETECTAR OBJETO PLANO ============================================================================= */ type IsPlainObject = T extends object ? T extends Function ? false : T extends readonly any[] ? false : true : false /* ============================================================================= FILTRO RECURSIVO POR CAMPOS ============================================================================= */ type FiltroCampos = { [K in keyof T]?: IsPlainObject extends true ? tipoFiltro26 : PgOpsFor } /* ============================================================================= TIPO PRINCIPAL EXPORTADO ============================================================================= */ export type tipoFiltro26 = FiltroCampos & { /** * E => AND lógico * Todos os filtros dentro do array devem ser verdadeiros. */ E?: tipoFiltro26[] /** * OU => OR lógico * Pelo menos um filtro dentro do array deve ser verdadeiro. */ OU?: tipoFiltro26[] } /* ============================================================================= VALIDAÇÃO ESTRUTURAL (ZOD) ============================================================================= */ const zOperadores = z.nativeEnum(operadores26) const zValor = z.any() const zCondicao = z.record(zOperadores, zValor) export const zFiltro26: z.ZodType = 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 = (filtro: tipoFiltro26) => filtro const _filtro = criarFiltro26({ idade: { [operadores26[">="]]: 18 }, [agrupadores26.OU]: [ { nome: { [operadores26.like]: "%pa%" } }, { [agrupadores26.E]: [ { carro: { ano: { [operadores26["="]]: 2020 } } }, { carro: { modelo: { [operadores26.in]: ["Civic", "Corolla"] } } }, ], }, ], })