slonik-trpc
Version:
Slonik tRPC loader
259 lines (258 loc) • 14.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildView = void 0;
const slonik_1 = require("slonik");
const sqlUtils_1 = require("../helpers/sqlUtils");
const zod_1 = require("../helpers/zod");
const buildView = (parts, ...values) => {
var _a, _b;
const context = {};
const fromFragment = slonik_1.sql.fragment(parts, ...values.map(value => { var _a; return typeof value === 'function' ? (_a = value(context)) !== null && _a !== void 0 ? _a : null : value; }));
if (!fromFragment.sql.match(/^\s*FROM/i)) {
throw new Error("First part of view must be FROM");
}
let constraints = null;
const options = {};
const allColumns = {};
const preprocessors = [];
const config = {
table: (_a = fromFragment.sql.match(/^FROM\s*(\w+)/i)) === null || _a === void 0 ? void 0 : _a[1],
aliases: new Map(),
};
const identifierProxy = new Proxy({}, {
get(target, property) {
if (property === "_main")
return slonik_1.sql.identifier([
config.aliases.get(property) || config.table || "",
]);
return slonik_1.sql.identifier([
config.aliases.get(property) || property,
]);
},
});
if (!config.table) {
config.table = (_b = fromFragment.sql.match(/(AS|\))\s+(\w+)\s*$/i)) === null || _b === void 0 ? void 0 : _b[2];
}
const interpreters = {};
const getWhereConditions = (filters, ctx, opts) => __awaiter(void 0, void 0, void 0, function* () {
const realContext = Object.assign(Object.assign({}, context), ctx);
const realOptions = Object.assign(Object.assign({}, options), opts);
const postprocessedFilters = yield preprocessors.slice(-1).reduce((acc, preprocessor) => __awaiter(void 0, void 0, void 0, function* () {
const filters = yield acc;
return preprocessor(filters, realContext);
}), filters);
const authConditions = constraints && !(realOptions === null || realOptions === void 0 ? void 0 : realOptions.bypassConstraints) ? yield constraints(realContext) : null;
const auth = Array.isArray(authConditions) ? authConditions : [authConditions].filter(zod_1.notEmpty);
const conditions = yield interpretFilter(postprocessedFilters || filters, interpreters, realContext, realOptions);
return [
...auth,
...conditions,
];
});
const getWhereFragment = (filters, context, options) => __awaiter(void 0, void 0, void 0, function* () {
const conditions = yield getWhereConditions(filters, context, options);
return conditions.length
? slonik_1.sql.fragment `WHERE \n(${slonik_1.sql.join(conditions, slonik_1.sql.fragment `)\nAND (`)})\n`
: slonik_1.sql.fragment ``;
});
const getFromFragment = (ctx = {}) => {
return slonik_1.sql.fragment(parts, ...values.map(value => { var _a; return typeof value === 'function' ? (_a = value(ctx)) !== null && _a !== void 0 ? _a : null : value; }));
};
const addFilter = (interpreter, fields, mapper, ...otherArgs) => {
if (mapper && Array.isArray(fields) && fields.length > 1) {
throw new Error("If you specify a mapper function you cannot have multiple filter keys");
}
return self.addFilters((Array.isArray(fields) ? fields : [fields]).reduce((acc, key) => {
return Object.assign(Object.assign({}, acc), { [key]: (value, allFilters, ctx, key) => {
const keys = key.split(".");
if (keys.length > 2) {
// Ignore middle keys (earlier prefixes), only first and last matter
keys.splice(1, keys.length - 2);
}
const identifier = mapper
? // Try to get the table name from the 2nd to last prefix if it exists, if not then use main table
typeof mapper === 'function' ? mapper(identifierProxy, value, allFilters, ctx) : mapper
: config.table && keys.length <= 1
? slonik_1.sql.identifier([
config.table,
...keys.slice(-1),
])
: slonik_1.sql.identifier([...keys.slice(-2)]);
return interpreter(value, identifier, ...otherArgs);
} });
}, {}));
};
const self = Object.assign(Object.assign({}, fromFragment), { getFromFragment,
addFilters(filters) {
Object.assign(interpreters, filters);
return self;
},
setTableAliases(newAliases) {
for (const [key, value] of Object.entries(newAliases)) {
config.aliases.set(key, value);
}
return self;
},
setFilterPreprocess(preprocess) {
preprocessors.push(preprocess);
return self;
},
getFilters(options) {
var _a;
let prefix = (options === null || options === void 0 ? void 0 : options.table) || "";
if (prefix && !prefix.endsWith(".")) {
prefix += ".";
}
const exclude = ((options === null || options === void 0 ? void 0 : options.exclude) || []);
const include = ((options === null || options === void 0 ? void 0 : options.include) || []);
const filters = {};
for (const key of Object.keys(interpreters)) {
// exclude may have * wildcards that exclude all filters that start with the prefix
if (exclude.some((ex) => ex.endsWith("*")
? key.startsWith(ex.replace("*", ""))
: ex === key)) {
continue;
}
const isIncluded = !include.length ||
include.some((ex) => ex.endsWith("*")
? key.startsWith(ex.replace("*", ""))
: ex === key);
if (isIncluded) {
filters[prefix + key.replace(prefix, "")] = {
interpret: ((_a = interpreters[key]) === null || _a === void 0 ? void 0 : _a.interpret) ||
interpreters[key],
prefix: key.startsWith(prefix) ? "" : prefix,
};
}
}
return filters;
}, addStringFilter: (keys, name) => {
return addFilter(sqlUtils_1.stringFilter, keys, name);
}, addComparisonFilter: (keys, name, ...otherArgs) => {
return addFilter(sqlUtils_1.comparisonFilter, keys, name, ...otherArgs);
}, addBooleanFilter: (keys, name, ...otherArgs) => {
return addFilter(sqlUtils_1.booleanFilter, keys, name, ...otherArgs);
}, addJsonContainsFilter: (keys, name) => {
return addFilter(sqlUtils_1.jsonbContainsFilter, keys, name);
}, addDateFilter: (keys, name) => {
return addFilter(sqlUtils_1.dateFilter, keys, name);
}, addInArrayFilter: (keys, name, type) => {
const arrFilter = type ? (0, sqlUtils_1.arrayDynamicFilter)(type) : sqlUtils_1.arrayFilter;
return addFilter(arrFilter, keys, name);
}, addGenericFilter: (name, interpret) => {
return addFilter(sqlUtils_1.genericFilter, name, (table, value, ...args) => {
return interpret(value, ...args);
});
}, options: (opts) => {
if (opts && typeof opts === 'object') {
for (const [key, value] of Object.entries(opts)) {
options[key] = value;
}
}
return self;
}, context: (ctx) => {
if (ctx && typeof ctx === 'object') {
for (const [key, value] of Object.entries(ctx)) {
context[key] = value;
}
}
return self;
}, setConstraints(cons) {
constraints = cons;
return self;
}, getWhereConditions: (args) => __awaiter(void 0, void 0, void 0, function* () {
return getWhereConditions(args.where, args.ctx, args.options);
}), load: (args) => __awaiter(void 0, void 0, void 0, function* () {
const db = args.db || options.db;
if (!db) {
throw new Error('Database is not set. Please set the database by calling options({ db: db })');
}
if (args.take === 0)
return [];
const realContext = Object.assign(Object.assign({}, context), args.ctx);
const whereFragment = args.where
? yield getWhereFragment(args.where, realContext, options)
: slonik_1.sql.fragment ``;
const selectFrag = Array.isArray(args.select) ?
slonik_1.sql.fragment `SELECT ${slonik_1.sql.join(args.select.map(key => allColumns[key]).filter((frag) => {
return frag && (frag.sql || frag.type);
}), slonik_1.sql.fragment `\n, `)}` : args.select;
const query = slonik_1.sql.unsafe `${selectFrag} ${getFromFragment(realContext)} ${whereFragment}
${args.groupBy ? slonik_1.sql.fragment `GROUP BY ${args.groupBy}` : slonik_1.sql.fragment ``}
${args.orderBy ? slonik_1.sql.fragment `ORDER BY ${args.orderBy}` : slonik_1.sql.fragment ``}
${typeof args.take === 'number' && args.take > 0 ? slonik_1.sql.fragment `LIMIT ${args.take}` : slonik_1.sql.fragment ``}
${typeof args.skip === 'number' && args.skip > 0 ? slonik_1.sql.fragment `OFFSET ${args.skip}` : slonik_1.sql.fragment ``}
`;
return db.any(query);
}), setColumns: (columns) => {
if (Array.isArray(columns)) {
for (const column of columns) {
if (typeof column === 'string') {
allColumns[column] = slonik_1.sql.identifier([column]);
}
}
}
else if (typeof columns === 'object') {
Object.keys(columns).forEach(key => columns[key] &&
(allColumns[key] = columns[key]));
}
return self;
}, getWhereFragment: (args) => __awaiter(void 0, void 0, void 0, function* () {
return getWhereFragment(args.where, args.ctx, args.options);
}) });
return self;
};
exports.buildView = buildView;
const interpretFilter = (filter, interpreters, context, options) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c;
const conditions = [];
const addCondition = (item) => item && conditions.push(item);
for (const key of Object.keys(filter)) {
const interpreter = interpreters[key];
const condition = yield ((_a = ((interpreter === null || interpreter === void 0 ? void 0 : interpreter.interpret) || interpreter)) === null || _a === void 0 ? void 0 : _a(filter[key], filter, context, key));
if (condition) {
addCondition(condition);
}
}
if ((_b = filter.OR) === null || _b === void 0 ? void 0 : _b.length) {
if (!(options === null || options === void 0 ? void 0 : options.orEnabled)) {
throw new Error("OR filters are not enabled. Please enable by passing { orFilterEnabled: true } in the options");
}
const orConditions = yield Promise.all(filter.OR.map((or) => __awaiter(void 0, void 0, void 0, function* () {
const orFilter = yield interpretFilter(or, interpreters, context, options);
return (orFilter === null || orFilter === void 0 ? void 0 : orFilter.length)
? slonik_1.sql.fragment `(${slonik_1.sql.join(orFilter, slonik_1.sql.fragment `) AND (`)})`
: null;
}))).then((filters) => filters.filter(zod_1.notEmpty));
if (orConditions === null || orConditions === void 0 ? void 0 : orConditions.length) {
addCondition(slonik_1.sql.fragment `(${slonik_1.sql.join(orConditions, slonik_1.sql.fragment `)\n OR (`)})`);
}
}
if ((_c = filter.AND) === null || _c === void 0 ? void 0 : _c.length) {
const andConditions = yield Promise.all(filter.AND.map((and) => __awaiter(void 0, void 0, void 0, function* () {
const andFilter = yield interpretFilter(and, interpreters, context, options);
return (andFilter === null || andFilter === void 0 ? void 0 : andFilter.length)
? slonik_1.sql.fragment `(${slonik_1.sql.join(andFilter, slonik_1.sql.fragment `) AND (`)})`
: null;
}))).then((filters) => filters.filter(zod_1.notEmpty));
if (andConditions === null || andConditions === void 0 ? void 0 : andConditions.length) {
addCondition(slonik_1.sql.fragment `(${slonik_1.sql.join(andConditions, slonik_1.sql.fragment `)\n AND (`)})`);
}
}
if (filter.NOT) {
const notFilter = yield interpretFilter(filter.NOT, interpreters, context, options);
if (notFilter.length) {
addCondition(slonik_1.sql.fragment `NOT (${slonik_1.sql.join(notFilter, slonik_1.sql.fragment `) AND (`)})`);
}
}
return conditions;
});