|
|
|
@ -2,6 +2,8 @@
|
|
|
|
|
|
|
|
|
|
const assert = require("assert");
|
|
|
|
|
const matchValue = require("match-value");
|
|
|
|
|
const dbErrors = require("db-errors");
|
|
|
|
|
const DataLoader = require("dataloader");
|
|
|
|
|
|
|
|
|
|
const knexIntrospect = require("./introspect");
|
|
|
|
|
const dataloaderFromKnexResults = require("./dataloader-from-knex-results");
|
|
|
|
@ -12,6 +14,14 @@ const dataloaderFromKnexResults = require("./dataloader-from-knex-results");
|
|
|
|
|
- Update record: give each type an update method for that individual record, by ID? For "get previous value, then set new value" style usecases
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
async function wrapErrors(callback) {
|
|
|
|
|
try {
|
|
|
|
|
return callback();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw dbErrors.wrapError(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyFilterPredicate(query, column, predicate, negate = false) {
|
|
|
|
|
if (predicate.__comparator === "not") {
|
|
|
|
|
return applyFilterPredicate(query, column, predicate.comparator, true);
|
|
|
|
@ -85,8 +95,13 @@ module.exports = {
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
generateModule: async function generate(globalKnex, tableName, relations = {}) {
|
|
|
|
|
// Make sure we only look up each table once, even when resolving relations
|
|
|
|
|
let introspectLoader = new DataLoader((tables) => {
|
|
|
|
|
return Promise.all(tables.map((table) => knexIntrospect(globalKnex, table)));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// relations: { columnName: [ belongsTo, hasMany ] }, eg. on a posts table, it might have { thread_id: [ "thread", "posts" ] }
|
|
|
|
|
let tableLayout = await knexIntrospect(globalKnex, tableName);
|
|
|
|
|
let tableLayout = await introspectLoader.load(tableName);
|
|
|
|
|
|
|
|
|
|
let fieldNames = Object.keys(tableLayout);
|
|
|
|
|
let primaryKeyColumn = Object.values(tableLayout).find((props) => props.isPrimaryKey === true);
|
|
|
|
@ -111,8 +126,10 @@ module.exports = {
|
|
|
|
|
remoteTypes[remoteTypeName][remoteField] = async function(_, { $make, $getProperty, knex }) {
|
|
|
|
|
let tx = (knex ?? globalKnex);
|
|
|
|
|
|
|
|
|
|
let result = await tx(tableName).columns("id").where({
|
|
|
|
|
[idFieldName]: await $getProperty(this, primaryKey)
|
|
|
|
|
let result = await wrapErrors(async () => {
|
|
|
|
|
return tx(tableName).columns("id").where({
|
|
|
|
|
[idFieldName]: await $getProperty(this, primaryKey)
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result.map((item) => {
|
|
|
|
@ -139,10 +156,15 @@ module.exports = {
|
|
|
|
|
types: {
|
|
|
|
|
[typeName]: async function({ id }) {
|
|
|
|
|
let normalFields = fieldNames.map((name) => {
|
|
|
|
|
return [ name, async (_, { dlayerKnexTable }) => {
|
|
|
|
|
let record = await dlayerKnexTable.load(id);
|
|
|
|
|
return record[name];
|
|
|
|
|
}];
|
|
|
|
|
if (name === primaryKey) {
|
|
|
|
|
// No point making a trip to the database if all we need is an ID we already have
|
|
|
|
|
return [ name, id ];
|
|
|
|
|
} else {
|
|
|
|
|
return [ name, async (_, { dlayerKnexTable }) => {
|
|
|
|
|
let record = await dlayerKnexTable.load(id);
|
|
|
|
|
return record[name];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let relationFields = [];
|
|
|
|
@ -150,7 +172,7 @@ module.exports = {
|
|
|
|
|
for (let name of Object.keys(relations)) {
|
|
|
|
|
let localField = relations[name][0]; // eg. "thread"
|
|
|
|
|
let foreignKey = tableLayout[name].foreignKey;
|
|
|
|
|
let remoteTableLayout = await knexIntrospect(globalKnex, foreignKey.table);
|
|
|
|
|
let remoteTableLayout = await introspectLoader.load(foreignKey.table);
|
|
|
|
|
|
|
|
|
|
if (remoteTableLayout[foreignKey.column].isPrimaryKey === true) {
|
|
|
|
|
let field = [ localField, async function (_, { $make, $getProperty }) {
|
|
|
|
@ -188,7 +210,7 @@ module.exports = {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (orderBy != null) {
|
|
|
|
|
let ascending = orderBy.startsWith("-");
|
|
|
|
|
let ascending = !orderBy.startsWith("-");
|
|
|
|
|
|
|
|
|
|
let orderField = (ascending === true)
|
|
|
|
|
? orderBy
|
|
|
|
@ -205,7 +227,9 @@ module.exports = {
|
|
|
|
|
query = query.limit(limit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let result = await query;
|
|
|
|
|
let result = await wrapErrors(async () => {
|
|
|
|
|
return query;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// TODO: Map result(s) to types
|
|
|
|
|
if (first === true) {
|
|
|
|
@ -227,7 +251,9 @@ module.exports = {
|
|
|
|
|
let query = tx(tableName).delete();
|
|
|
|
|
query = applyFilterArgument(query, filter);
|
|
|
|
|
|
|
|
|
|
let deletedRowCount = await query;
|
|
|
|
|
let deletedRowCount = await wrapErrors(async () => {
|
|
|
|
|
return query;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { count: deletedRowCount };
|
|
|
|
|
},
|
|
|
|
@ -246,7 +272,9 @@ module.exports = {
|
|
|
|
|
applyFilterArgument(query, filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let affected = await query;
|
|
|
|
|
let affected = await wrapErrors(async () => {
|
|
|
|
|
return query;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return affected.map((item) => {
|
|
|
|
|
dlayerKnexTable.clear(item.id); // yeet from dataloader cache, so that we fetch the updated version instead
|
|
|
|
@ -263,7 +291,9 @@ module.exports = {
|
|
|
|
|
.insert(values)
|
|
|
|
|
.returning("id");
|
|
|
|
|
|
|
|
|
|
let result = await query;
|
|
|
|
|
let result = await wrapErrors(async () => {
|
|
|
|
|
return query;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isArray) {
|
|
|
|
|
return result.map((item) => {
|
|
|
|
|