Compare commits

...

4 Commits

@ -7,6 +7,7 @@
"license": "WTFPL OR CC0-1.0", "license": "WTFPL OR CC0-1.0",
"dependencies": { "dependencies": {
"dataloader": "^2.2.2", "dataloader": "^2.2.2",
"db-errors": "^0.2.3",
"match-value": "^1.1.0", "match-value": "^1.1.0",
"syncpipe": "^1.0.0" "syncpipe": "^1.0.0"
} }

@ -2,6 +2,8 @@
const assert = require("assert"); const assert = require("assert");
const matchValue = require("match-value"); const matchValue = require("match-value");
const dbErrors = require("db-errors");
const DataLoader = require("dataloader");
const knexIntrospect = require("./introspect"); const knexIntrospect = require("./introspect");
const dataloaderFromKnexResults = require("./dataloader-from-knex-results"); 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 - 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) { function applyFilterPredicate(query, column, predicate, negate = false) {
if (predicate.__comparator === "not") { if (predicate.__comparator === "not") {
return applyFilterPredicate(query, column, predicate.comparator, true); return applyFilterPredicate(query, column, predicate.comparator, true);
@ -85,8 +95,13 @@ module.exports = {
}; };
}, },
generateModule: async function generate(globalKnex, tableName, relations = {}) { 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" ] } // 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 fieldNames = Object.keys(tableLayout);
let primaryKeyColumn = Object.values(tableLayout).find((props) => props.isPrimaryKey === true); let primaryKeyColumn = Object.values(tableLayout).find((props) => props.isPrimaryKey === true);
@ -111,8 +126,10 @@ module.exports = {
remoteTypes[remoteTypeName][remoteField] = async function(_, { $make, $getProperty, knex }) { remoteTypes[remoteTypeName][remoteField] = async function(_, { $make, $getProperty, knex }) {
let tx = (knex ?? globalKnex); let tx = (knex ?? globalKnex);
let result = await tx(tableName).columns("id").where({ let result = await wrapErrors(async () => {
[idFieldName]: await $getProperty(this, primaryKey) return tx(tableName).columns("id").where({
[idFieldName]: await $getProperty(this, primaryKey)
});
}); });
return result.map((item) => { return result.map((item) => {
@ -139,10 +156,15 @@ module.exports = {
types: { types: {
[typeName]: async function({ id }) { [typeName]: async function({ id }) {
let normalFields = fieldNames.map((name) => { let normalFields = fieldNames.map((name) => {
return [ name, async (_, { dlayerKnexTable }) => { if (name === primaryKey) {
let record = await dlayerKnexTable.load(id); // No point making a trip to the database if all we need is an ID we already have
return record[name]; return [ name, id ];
}]; } else {
return [ name, async (_, { dlayerKnexTable }) => {
let record = await dlayerKnexTable.load(id);
return record[name];
}];
}
}); });
let relationFields = []; let relationFields = [];
@ -150,7 +172,7 @@ module.exports = {
for (let name of Object.keys(relations)) { for (let name of Object.keys(relations)) {
let localField = relations[name][0]; // eg. "thread" let localField = relations[name][0]; // eg. "thread"
let foreignKey = tableLayout[name].foreignKey; 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) { if (remoteTableLayout[foreignKey.column].isPrimaryKey === true) {
let field = [ localField, async function (_, { $make, $getProperty }) { let field = [ localField, async function (_, { $make, $getProperty }) {
@ -188,7 +210,7 @@ module.exports = {
} }
if (orderBy != null) { if (orderBy != null) {
let ascending = orderBy.startsWith("-"); let ascending = !orderBy.startsWith("-");
let orderField = (ascending === true) let orderField = (ascending === true)
? orderBy ? orderBy
@ -205,7 +227,9 @@ module.exports = {
query = query.limit(limit); query = query.limit(limit);
} }
let result = await query; let result = await wrapErrors(async () => {
return query;
});
// TODO: Map result(s) to types // TODO: Map result(s) to types
if (first === true) { if (first === true) {
@ -227,7 +251,9 @@ module.exports = {
let query = tx(tableName).delete(); let query = tx(tableName).delete();
query = applyFilterArgument(query, filter); query = applyFilterArgument(query, filter);
let deletedRowCount = await query; let deletedRowCount = await wrapErrors(async () => {
return query;
});
return { count: deletedRowCount }; return { count: deletedRowCount };
}, },
@ -246,7 +272,9 @@ module.exports = {
applyFilterArgument(query, filter); applyFilterArgument(query, filter);
} }
let affected = await query; let affected = await wrapErrors(async () => {
return query;
});
return affected.map((item) => { return affected.map((item) => {
dlayerKnexTable.clear(item.id); // yeet from dataloader cache, so that we fetch the updated version instead dlayerKnexTable.clear(item.id); // yeet from dataloader cache, so that we fetch the updated version instead
@ -263,7 +291,9 @@ module.exports = {
.insert(values) .insert(values)
.returning("id"); .returning("id");
let result = await query; let result = await wrapErrors(async () => {
return query;
});
if (isArray) { if (isArray) {
return result.map((item) => { return result.map((item) => {

@ -12,6 +12,11 @@ dataloader@^2.2.2:
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g== integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
db-errors@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2"
integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng==
match-value@^1.1.0: match-value@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/match-value/-/match-value-1.1.0.tgz#ad311ef8bbe2d344a53ec3104e28fe221984b98e" resolved "https://registry.yarnpkg.com/match-value/-/match-value-1.1.0.tgz#ad311ef8bbe2d344a53ec3104e28fe221984b98e"

Loading…
Cancel
Save