Skip to content

Commit

Permalink
feat: addt'l createTable forms, coercion config, modularize, cjs export
Browse files Browse the repository at this point in the history
Add two new forms to createTable, in addition to the existing Array form: Object syntax & Function syntax. Function syntax allows passing the same function as you might use with knex itself for greater control over table schema.

Introduce a coercion option to allow control over how trilogy handles boolean values on insert / update / retrieval.

Create `helpers` and move almost all previous static methdos into it - these are functions like `parseResponse` and such that shouldn't necessarily be exposed.

Drop `lodash.foreach` in favor of functions written in `util.js`.

Drop `is-plain-obj` in favor of a function written in `util.js`.

Replace `native-or-lie` with `bluebird`.

Remove all flowtypes from the code.

Remove all inline JSDoc comments. These will be replaced by external documentation.

Centralize default values into `constants` like `where` and `columns` to avoid creating new Objects or Arrays all the time. 

Use `let` instead of `const` for most variables, for 2 main reasons: 1) stylistic and 2) const is kind of a flimsy shield.

Use `module.exports` commonjs syntax rather than `export default` ES2015 syntax to allow commonjs users to `require` trilogy without using `.default`.

BREAKING CHANGE: Any commonjs users `require`ing trilogy no longer need `.default` due to using commonjs export instead of Babel's `export default` fill.
  • Loading branch information
citycide committed Dec 17, 2016
1 parent 5de888b commit d3bab02
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 858 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,9 @@
"dependencies": {
"arify": "^0.7.0",
"babel-runtime": "^6.11.6",
"bluebird": "^3.4.6",
"fs-jetpack": "^0.9.2",
"is-plain-obj": "^1.1.0",
"knex": "^0.11.10",
"lodash.foreach": "^4.5.0",
"native-or-lie": "^1.0.2",
"sql.js": "^0.3.2"
},
"devDependencies": {
Expand Down
13 changes: 8 additions & 5 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
export default {
ERR_UNKNOWN:
`an unknown error occurred. Check the stacktrace or report an
issue if there is a problem with trilogy itself.`,
'an unknown error occurred. Check the stacktrace or report an ' +
'issue if there is a problem with trilogy itself.',
ERR_COL_MISSING:
`column name is required. Pass it as an independent argument
or as dot-notation along with the table argument.`,
ERR_NO_DATABASE: `Could not write - no database initialized.`
'column name is required. Pass it as an independent argument ' +
'or as dot-notation along with the table argument.',
ERR_NO_DATABASE: 'could not write - no database initialized.',

DEFAULT_WHERE: {},
DEFAULT_COLUMNS: ['*']
}
174 changes: 174 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { map, each, isBoolean, isFunction, isObject, isString } from './util'

export let coercion = { active: true }

export function parseResponse (contents) {
if (contents.length) {
let columns = contents[0].columns
let values = contents[0].values
let results = []
for (let i = 0; i < values.length; i++) {
let line = {}
for (let j = 0; j < columns.length; j++) {
line[columns[j]] = coercion.active
? stringToBoolean(values[i][j])
: values[i][j]
}
results.push(line)
}
return results
} else {
return []
}
}

// parse a dot or bracket notated string into table, column, & row
// the row value isn't actually used currently
export function parseTablePath (table, column, row) {
if (table.includes('.')) {
let [top, inner, nested] = table.split('.')
return parseTablePath(top, inner, nested)
} else if (table.includes('[')) {
let opener = table.indexOf('[')
let closer = table.indexOf(']', opener)

let top = table.substr(0, opener)
let inner = table.slice(opener + 1, closer)

let rowIndex = top.length + inner.length + 2

let extra, nested
if (rowIndex < table.length) {
extra = table.slice(rowIndex + 1)
let rowCloser = extra.indexOf(']')
nested = extra.substr(0, rowCloser)
}

return parseTablePath(top, inner, nested)
} else {
return [table, column, row]
}
}

export function sanitizeOrder (order, partial) {
if (Array.isArray(order) && order.length === 2) {
return partial.orderBy(...order)
} else if (isString(order)) {
return partial.orderBy(order)
} else {
return partial
}
}

export function sanitizeWhere (where, partial) {
if (Array.isArray(where)) {
let arr = coercion.active
? where.map(booleanToString) : where
return partial.where(...arr)
} else if (isFunction(where)) {
return partial.where(where.bind(partial))
} else {
// it's an object
return partial.where(map(where, v => {
return coercion.active ? booleanToString(v) : v
}))
}
}

export function getConflictString (conflict) {
switch (conflict.toLowerCase()) {
case 'fail': return ' or fail '
case 'abort': return ' or abort '
case 'ignore': return ' or ignore '
case 'replace': return ' or replace '
case 'rollback': return ' or rollback '
default: return ' '
}
}

export function sanitizeColumns (columns) {
if (Array.isArray(columns)) return columns
if (isString(columns)) return [columns]
return ['*']
}

export function isValidWhere (where) {
if (isObject(where)) return true

if (Array.isArray(where)) {
let len = where.length
return len === 2 || len === 3
}

return false
}

export function processColumn (table, column) {
if (!column.name) {
throw new Error('column name required')
}

let { name, type = 'text' } = column

if (column.unique === 'inline') {
// bypass knex's usual unique method
column['__TYPE__'] = `${type} unique`
type = 'specificType'
delete column.unique
}

let partial = table[type](name, column['__TYPE__'])

mapColumnProperties(partial, column)
}

export function processArraySchema (table, columns) {
each(columns, column => {
if (isString(column)) {
table.text(column)
return
}

if (isObject(column)) {
processColumn(table, column)
}
})
}

export function processObjectSchema (table, columns) {
each(columns, (value, name) => {
if (isString(value) && isFunction(table[value])) {
table[value](name)
} else if (isObject(value)) {
let column = Object.assign({}, { name }, value)
processColumn(table, column)
}
})
}

export function mapColumnProperties (partial, column) {
return Object.keys(column).reduce((acc, key) => {
// name & type are handled already
if (key === 'name' || key === 'type') return acc

let value = column[key]
let method = acc[key]

if (typeof method !== 'function') {
return
}

return value === undefined
? method.call(acc)
: method.call(acc, value)
}, partial)
}

export function stringToBoolean (value) {
if (value !== 'true' && value !== 'false') return value
return value === 'true'
}

export function booleanToString (value) {
return isBoolean ? `${value}` : value
}
Loading

0 comments on commit d3bab02

Please sign in to comment.