Created
September 22, 2018 11:29
-
-
Save elnygren/a2607c213e62a42c2b9578667094239b to your computer and use it in GitHub Desktop.
Knex with query logging and camelCasing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference path="./identifierMappings-types.d.ts" /> | |
import * as knex from 'knex'; | |
import * as config from '../config' | |
import { knexSnakeCaseMappers } from './identifierMappings' | |
const db: knex = knex({ | |
client: 'pg', | |
connection: config.DATABASE_URL, | |
...knexSnakeCaseMappers(), | |
}) | |
if (config.NODE_ENV === 'development') { | |
db.on('query', (data: any) => { | |
console.log('SQL:', data.sql, `(${data.bindings})`) | |
}) | |
} | |
export default db |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface KnexMappers { | |
wrapIdentifier(identifier: string, origWrap: (identifier: string) => string): string; | |
postProcessResponse(response: any): any; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/Vincit/objection.js/blob/master/lib/utils/identifierMapping.js | |
// tslint:disable | |
// Super fast memoize for single argument functions. | |
function memoize(func) { | |
const cache = new Map(); | |
return input => { | |
let output = cache.get(input); | |
if (output === undefined) { | |
output = func(input); | |
cache.set(input, output); | |
} | |
return output; | |
}; | |
} | |
// camelCase to snake_case converter that also works with | |
// non-ascii characters. | |
function snakeCase(str, upperCase = false) { | |
if (str.length === 0) { | |
return str; | |
} | |
const upper = str.toUpperCase(); | |
const lower = str.toLowerCase(); | |
let out = lower[0]; | |
for (let i = 1, l = str.length; i < l; ++i) { | |
const char = str[i]; | |
const prevChar = str[i - 1]; | |
const upperChar = upper[i]; | |
const prevUpperChar = upper[i - 1]; | |
const lowerChar = lower[i]; | |
const prevLowerChar = lower[i - 1]; | |
// Test if `char` is an upper-case character and that the character | |
// actually has different upper and lower case versions. | |
if (char === upperChar && upperChar !== lowerChar) { | |
// Multiple consecutive upper case characters shouldn't add underscores. | |
// For example "fooBAR" should be converted to "foo_bar". | |
if (prevChar === prevUpperChar && prevUpperChar !== prevLowerChar) { | |
out += lowerChar; | |
} else { | |
out += '_' + lowerChar; | |
} | |
} else { | |
out += char; | |
} | |
} | |
if (upperCase) { | |
return out.toUpperCase(); | |
} else { | |
return out; | |
} | |
} | |
// snake_case to camelCase converter that simply reverses | |
// the actions done by `snakeCase` function. | |
function camelCase(str, upperCase = false) { | |
if (str.length === 0) { | |
return str; | |
} | |
if (upperCase && isAllUpperCaseSnakeCase(str)) { | |
// Only convert to lower case if the string is all upper | |
// case snake_case. This allowes camelCase strings to go | |
// through without changing. | |
str = str.toLowerCase(); | |
} | |
let out = str[0]; | |
for (let i = 1, l = str.length; i < l; ++i) { | |
const char = str[i]; | |
const prevChar = str[i - 1]; | |
if (char !== '_') { | |
if (prevChar === '_') { | |
out += char.toUpperCase(); | |
} else { | |
out += char; | |
} | |
} | |
} | |
return out; | |
} | |
function isAllUpperCaseSnakeCase(str) { | |
for (let i = 1, l = str.length; i < l; ++i) { | |
const char = str[i]; | |
if (char !== '_' && char !== char.toUpperCase()) { | |
return false; | |
} | |
} | |
return true; | |
} | |
// Returns a function that splits the inputs string into pieces using `separator`, | |
// only calls `mapper` for the last part and concatenates the string back together. | |
// If no separators are found, `mapper` is called for the entire string. | |
function mapLastPart(mapper, separator) { | |
return str => { | |
const idx = str.lastIndexOf(separator); | |
const mapped = mapper(str.slice(idx + separator.length)); | |
return str.slice(0, idx + separator.length) + mapped; | |
}; | |
} | |
// Returns a function that takes an object as an input and maps the object's keys | |
// using `mapper`. If the input is not an object, the input is returned unchanged. | |
function keyMapper(mapper) { | |
return obj => { | |
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) { | |
return obj; | |
} | |
const keys = Object.keys(obj); | |
const out = {}; | |
for (let i = 0, l = keys.length; i < l; ++i) { | |
const key = keys[i]; | |
out[mapper(key)] = obj[key]; | |
} | |
return out; | |
}; | |
} | |
function snakeCaseMappers({ upperCase = false } = {}) { | |
return { | |
parse: keyMapper(memoize(str => camelCase(str, upperCase))), | |
format: keyMapper(memoize(str => snakeCase(str, upperCase))) | |
}; | |
} | |
function knexIdentifierMappers({ parse, format, idSeparator = ':' } = {}) { | |
const formatId = memoize(mapLastPart(format, idSeparator)); | |
const parseId = memoize(mapLastPart(parse, idSeparator)); | |
const parseKeys = keyMapper(parseId); | |
return { | |
wrapIdentifier(identifier, origWrap) { | |
return origWrap(formatId(identifier)); | |
}, | |
postProcessResponse(result) { | |
if (Array.isArray(result)) { | |
const output = new Array(result.length); | |
for (let i = 0, l = result.length; i < l; ++i) { | |
output[i] = parseKeys(result[i]); | |
} | |
return output; | |
} else { | |
return parseKeys(result); | |
} | |
} | |
}; | |
} | |
function knexSnakeCaseMappers({ upperCase = false } = {}) { | |
return knexIdentifierMappers({ | |
parse: str => camelCase(str, upperCase), | |
format: str => snakeCase(str, upperCase) | |
}); | |
} | |
function knexIdentifierMapping(colToProp) { | |
const propToCol = Object.keys(colToProp).reduce((propToCol, column) => { | |
propToCol[colToProp[column]] = column; | |
return propToCol; | |
}, {}); | |
return knexIdentifierMappers({ | |
parse: column => colToProp[column] || column, | |
format: prop => propToCol[prop] || prop | |
}); | |
} | |
module.exports = { | |
snakeCase, | |
camelCase, | |
snakeCaseMappers, | |
knexSnakeCaseMappers, | |
knexIdentifierMappers, | |
knexIdentifierMapping, | |
camelCaseKeys: keyMapper(memoize(camelCase)), | |
snakeCaseKeys: keyMapper(memoize(snakeCase)) | |
}; | |
// tslint:enable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment