slonik-trpc
Version:
Slonik tRPC loader
241 lines (240 loc) • 10.8 kB
JavaScript
;
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;