UNPKG

slonik-trpc

Version:
241 lines (240 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.jsonbContainsFilter = exports.jsonbFilter = exports.stringFilter = exports.stringFilterType = exports.comparisonFilter = exports.comparisonFilterType = exports.invertFilter = exports.arrayFilter = exports.arrayDynamicFilter = exports.dateFilter = exports.genericFilter = exports.booleanFilter = exports.dateFilterType = exports.arrayStringFilterType = exports.rowsToArray = exports.rowToJson = exports.arrayifyType = void 0; const zod_1 = require("zod"); const slonik_1 = require("slonik"); const zod_2 = require("./zod"); const arrayifyType = (type) => zod_1.z.preprocess((a) => (Array.isArray(a) ? a : [a].filter(zod_2.notEmpty)), zod_1.z.union([zod_1.z.array(type), type])); exports.arrayifyType = arrayifyType; const rowToJson = (fragment, name) => slonik_1.sql.fragment ` row_to_json((SELECT row FROM (${fragment}) row)) AS ${slonik_1.sql.identifier([name || 'row_to_json'])} `; exports.rowToJson = rowToJson; const rowsToArray = (fragment, fromFragment, name) => { const isString = typeof fromFragment === 'string'; return slonik_1.sql.fragment `( SELECT COALESCE(json_agg(all_rows), '[]') FROM (${fragment} ${isString || !fromFragment ? slonik_1.sql.fragment `` : fromFragment}) AS all_rows ) AS ${slonik_1.sql.identifier([name || (isString ? fromFragment : findTableName(fromFragment)) || 'rows_to_array'])}`; }; exports.rowsToArray = rowsToArray; exports.arrayStringFilterType = (0, exports.arrayifyType)(zod_1.z.string()); exports.dateFilterType = zod_1.z .object({ _lt: zod_1.z.string(), _gt: zod_1.z.string(), _lte: zod_1.z.string(), _gte: zod_1.z.string(), _is_null: zod_1.z.boolean(), }) .partial(); const booleanFilter = (bool, trueStatement, // If null, doesn't add anything on a false condition falseStatement) => { if (bool === true) { return trueStatement; } else if (bool === false && falseStatement !== null) { return falseStatement || (0, exports.invertFilter)(trueStatement); } return null; }; exports.booleanFilter = booleanFilter; const genericFilter = (value, statement) => { if (value !== null && value !== undefined) { return statement; } return null; }; exports.genericFilter = genericFilter; const findTableName = (fragment) => { var _a, _b; const table = (_a = fragment === null || fragment === void 0 ? void 0 : fragment.sql.match(/^\s*FROM\s+(\S+)/i)) === null || _a === void 0 ? void 0 : _a[1]; return (_b = table === null || table === void 0 ? void 0 : table.replace(/\W+/g, '')) === null || _b === void 0 ? void 0 : _b.toLowerCase(); }; const dateFilter = (date, field) => { return (0, exports.comparisonFilter)(date, field); }; exports.dateFilter = dateFilter; const arrayDynamicFilter = (type = 'text') => (filter, field, typeOverride) => { if (!Array.isArray(filter)) filter = [filter].filter(zod_2.notEmpty); if (filter.length) { return slonik_1.sql.fragment `${field} = ANY(${slonik_1.sql.array(filter, typeOverride || type)})`; } return null; }; exports.arrayDynamicFilter = arrayDynamicFilter; exports.arrayFilter = (0, exports.arrayDynamicFilter)(); const invertFilter = (condition) => { if (condition) { return slonik_1.sql.fragment `NOT ( ${condition} )`; } return null; }; exports.invertFilter = invertFilter; const numberString = zod_1.z.union([zod_1.z.number(), zod_1.z.string()]); exports.comparisonFilterType = zod_1.z.object({ _gt: numberString.optional(), _lt: numberString.optional(), _gte: numberString.optional(), _lte: numberString.optional(), _eq: numberString.optional(), _neq: numberString.optional(), _in: zod_1.z.union([(0, exports.arrayifyType)(zod_1.z.number()), exports.arrayStringFilterType]).optional(), _nin: zod_1.z.union([(0, exports.arrayifyType)(zod_1.z.number()), exports.arrayStringFilterType]).optional(), _is_null: zod_1.z.boolean().optional(), }); const comparisonFilter = (filter, field, type = 'text') => { var _a, _b; if (!filter) return null; const conditions = []; if (filter._gt !== undefined && filter._gt !== null) { conditions.push(slonik_1.sql.fragment `${field} > ${filter._gt}`); } if (filter._lt !== undefined && filter._lt !== null) { conditions.push(slonik_1.sql.fragment `${field} < ${filter._lt}`); } if (filter._gte !== undefined && filter._gte !== null) { conditions.push(slonik_1.sql.fragment `${field} >= ${filter._gte}`); } if (filter._lte !== undefined && filter._lte !== null) { conditions.push(slonik_1.sql.fragment `${field} <= ${filter._lte}`); } if (filter._eq !== undefined && filter._eq !== null) { conditions.push(slonik_1.sql.fragment `${field} = ${filter._eq}`); } if (filter._neq !== undefined && filter._neq !== null) { conditions.push(slonik_1.sql.fragment `${field} != ${filter._neq}`); } if (typeof filter._in !== 'number' && ((_a = filter._in) === null || _a === void 0 ? void 0 : _a.length)) { const fragment = (0, exports.arrayFilter)(filter._in, field, type); if (fragment) { conditions.push(fragment); } } if (typeof filter._nin !== 'number' && ((_b = filter._nin) === null || _b === void 0 ? void 0 : _b.length)) { const fragment = (0, exports.invertFilter)((0, exports.arrayFilter)(filter._nin, field, type)); if (fragment) { conditions.push(fragment); } } const isNull = filter._is_null || filter._eq === null; const isNotNull = filter._is_null === false || filter._neq === null; if (isNull || isNotNull) { conditions.push(isNull ? slonik_1.sql.fragment `${field} IS NULL` : slonik_1.sql.fragment `${field} IS NOT NULL`); } if (conditions.length) { return slonik_1.sql.fragment `(${slonik_1.sql.join(conditions, slonik_1.sql.fragment `) AND (`)})`; } return null; }; exports.comparisonFilter = comparisonFilter; /** * Use this for string comparisons with LIKE, ILIKE, etc. */ exports.stringFilterType = zod_1.z.union([zod_1.z.string(), zod_1.z.object({ _gt: zod_1.z.string().optional(), _lt: zod_1.z.string().optional(), _eq: zod_1.z.string().optional(), _neq: zod_1.z.string().optional(), _in: exports.arrayStringFilterType.optional(), _nin: exports.arrayStringFilterType.optional(), _is_null: zod_1.z.boolean().optional(), _ilike: zod_1.z.string().optional(), _like: zod_1.z.string().optional(), _nlike: zod_1.z.string().optional(), _nilike: zod_1.z.string().optional(), _regex: zod_1.z.string().optional(), _iregex: zod_1.z.string().optional(), _nregex: zod_1.z.string().optional(), _niregex: zod_1.z.string().optional(), })]); const stringFilter = (filter, field) => { const conditions = []; if (typeof filter === 'string') { return slonik_1.sql.fragment `(${field} = ${filter})`; } if (filter === null || filter === void 0 ? void 0 : filter._ilike) { conditions.push(slonik_1.sql.fragment `${field} ILIKE ${filter._ilike}`); } if (filter === null || filter === void 0 ? void 0 : filter._like) { conditions.push(slonik_1.sql.fragment `${field} LIKE ${filter._like}`); } if (filter === null || filter === void 0 ? void 0 : filter._nlike) { conditions.push(slonik_1.sql.fragment `${field} NOT LIKE ${filter._nlike}`); } if (filter === null || filter === void 0 ? void 0 : filter._nilike) { conditions.push(slonik_1.sql.fragment `${field} NOT ILIKE ${filter._nilike}`); } if (filter === null || filter === void 0 ? void 0 : filter._regex) { conditions.push(slonik_1.sql.fragment `${field} ~ ${filter._regex}`); } if (filter === null || filter === void 0 ? void 0 : filter._iregex) { conditions.push(slonik_1.sql.fragment `${field} ~* ${filter._iregex}`); } if (filter === null || filter === void 0 ? void 0 : filter._nregex) { conditions.push(slonik_1.sql.fragment `${field} !~ ${filter._nregex}`); } if (filter === null || filter === void 0 ? void 0 : filter._niregex) { conditions.push(slonik_1.sql.fragment `${field} !~* ${filter._niregex}`); } const fragment = (0, exports.comparisonFilter)(filter, field); if (fragment) { conditions.push(fragment); } if (conditions.length) { return slonik_1.sql.fragment `(${slonik_1.sql.join(conditions, slonik_1.sql.fragment `) AND (`)})`; } return null; }; exports.stringFilter = stringFilter; const jsonbFilter = (field, value, parentPath = []) => { const fullPath = [...parentPath, field]; if (value !== null && typeof value === 'object' && !Array.isArray(value)) { // Handle nested objects recursively const conditions = Object.entries(value).map(([key, val]) => (0, exports.jsonbFilter)(key, val, fullPath)).filter(zod_2.notEmpty); if (conditions.length === 0) return null; if (conditions.length === 1) return conditions[0]; return slonik_1.sql.fragment `(${slonik_1.sql.join(conditions, slonik_1.sql.fragment ` AND `)})`; } else { // Handle primitive values with appropriate casting const pathExpr = slonik_1.sql.join(fullPath.flatMap((el, idx) => { if (idx === 0) { return [slonik_1.sql.identifier([el])]; } else if (idx === fullPath.length - 1) { return [slonik_1.sql.fragment `->>`, slonik_1.sql.literalValue(el)]; } else { return [slonik_1.sql.fragment `->`, slonik_1.sql.literalValue(el)]; } }, ''), slonik_1.sql.fragment ``); if (typeof value === 'number') { return slonik_1.sql.fragment `(${pathExpr})::float8 = ${value}`; } else if (typeof value === 'boolean') { return slonik_1.sql.fragment `(${pathExpr})::bool = ${value}`; } else if (value !== undefined && typeof value !== 'object') { // For other types like string, no casting is necessary return slonik_1.sql.fragment `(${pathExpr}) = ${value}`; } else if (value === null && fullPath.length > 1) { // Only handles null values for nested objects return slonik_1.sql.fragment `(${pathExpr}) IS NULL`; } return null; } }; exports.jsonbFilter = jsonbFilter; const jsonbContainsFilter = (filter, field) => { if (filter) { return slonik_1.sql.fragment `(${field})::jsonb @> ${slonik_1.sql.jsonb(filter)}`; } return null; }; exports.jsonbContainsFilter = jsonbContainsFilter;