Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

leoric

Package Overview
Dependencies
Maintainers
1
Versions
141
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

leoric - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

History.md

66

lib/bone.js

@@ -113,3 +113,3 @@ 'use strict'

for (let name in this.constructor.schema) {
if (typeof this[name] !== 'undefined') {
if (this[name] != null) {
obj[name] = this[name]

@@ -120,3 +120,3 @@ }

for (let name in this) {
if (this.hasOwnProperty(name) && typeof this[name] !== 'undefined') {
if (this.hasOwnProperty(name) && this[name] != null) {
obj[name] = this[name]

@@ -178,3 +178,3 @@ }

const Model = this.constructor
const { schema, pool, primaryKey, table } = Model
const { schema, pool, primaryKey } = Model

@@ -208,3 +208,3 @@ if (schema.createdAt && !this.createdAt) this.createdAt = new Date()

return spell.upsert(data)
return spell.$upsert(data)
}

@@ -235,3 +235,3 @@

const Model = this.constructor
const { pool, primaryKey, schema, table } = Model
const { pool, primaryKey, schema } = Model
const data = {}

@@ -259,3 +259,3 @@

return spell.insert(data)
return spell.$insert(data)
}

@@ -497,3 +497,2 @@

static relate(name, opts) {

@@ -503,24 +502,3 @@ if (name in this.relations) {

}
const { foreignKey, hasMany, belongsTo, through, includes, select, where } = opts
const Model = this.reflectClass(opts.Model)
const relation = {
Model,
through,
includes,
foreignKey,
hasMany,
belongsTo,
where
}
if (select) {
const attributes = new Set(Array.from(Model.attributes).filter(Spell.parseSelect(select)))
for (const name of ['createdAt', 'updatedAt']) {
if (name in Model.schema) attributes.add(name)
}
relation.attributes = attributes
}
this.relations[name] = relation
this.relations[name] = { ...opts, Model: this.reflectClass(opts.Model) }
}

@@ -593,9 +571,9 @@

if (typeof conditions == 'object' && values.length == 1 && typeof values[0] == 'object') {
spell.where(conditions)
spell.$where(conditions)
for (const method of ['order', 'limit', 'offset', 'select']) {
const value = values[0][method]
if (value != null) spell[method](value)
if (value != null) spell[`$${method}`](value)
}
} else if (conditions) {
spell.where(conditions, ...values)
spell.$where(conditions, ...values)
}

@@ -607,9 +585,13 @@

static findOne(conditions, ...values) {
const spell = this.find(conditions, ...values).limit(1)
spell.promise = spell.promise.then(results => {
const spell = this.find(conditions, ...values).$limit(1)
spell.onresolve = results => {
return results.length > 0 && results[0] instanceof this ? results[0] : null
})
}
return spell
}
static include(...names) {
return this.find().$with(...names)
}
static where(conditions, ...values) {

@@ -620,15 +602,15 @@ return this.find(conditions, ...values)

static select(...attributes) {
return this.find().select(...attributes)
return this.find().$select(...attributes)
}
static group(...names) {
return this.find().group(...names)
return this.find().$group(...names)
}
static join(Model, conditions, ...values) {
return this.find().join(Model, conditions, ...values)
return this.find().$join(Model, conditions, ...values)
}
static count(conditions, ...values) {
return this.find(conditions, ...values).count()
return this.find(conditions, ...values).$count()
}

@@ -653,7 +635,7 @@

spell.where(conditions)
spell.$where(conditions)
if (this.schema.updatedAt && !values.updatedAt && !values.deletedAt) {
values.updatedAt = new Date()
}
spell.update(values)
spell.$update(values)

@@ -677,3 +659,3 @@ return spell

})
return spell.where(conditions).delete()
return spell.$where(conditions).$delete()
}

@@ -680,0 +662,0 @@ else if (this.schema.deletedAt) {

@@ -71,3 +71,2 @@ 'use strict'

if (value instanceof Spell) {
value.cancelled = true
result.push({

@@ -79,3 +78,3 @@ type: 'op',

}
else if (value != null && typeof value == 'object') {
else if (value != null && typeof value == 'object' && !Array.isArray(value) && Object.keys(value).length == 1) {
for (const $op in value) {

@@ -105,2 +104,14 @@ const op = OPERATOR_MAP[$op]

function parseSelect(select) {
const type = typeof select
if (type == 'function') return select
if (type == 'string') select = select.split(/\s+/)
if (Array.isArray(select)) {
return select.includes.bind(select)
} else {
throw new Error(`Invalid select ${select}`)
}
}
function formatOrders(spell) {

@@ -195,3 +206,3 @@ const formatOrder = ([name, order]) => {

function formatSelectWithoutJoin(spell) {
const { Model, attributes, whereConditions, groups, havingConditions, orders, limit, offset } = spell
const { Model, attributes, whereConditions, groups, havingConditions, orders, rowCount, skip } = spell
const chunks = []

@@ -230,4 +241,4 @@

if (orders.length > 0) chunks.push(`ORDER BY ${formatOrders(spell).join(', ')}`)
if (limit > 0) chunks.push(`LIMIT ${limit}`)
if (offset > 0) chunks.push(`OFFSET ${offset}`)
if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`)
if (skip > 0) chunks.push(`OFFSET ${skip}`)

@@ -246,11 +257,16 @@ return chunks.join(' ')

function formatSelectWithJoin(spell) {
const { Model, attributes, whereConditions, groups, orders, limit, offset, joins } = spell
const { Model, attributes, whereConditions, groups, orders, rowCount, skip, joins } = spell
const subspell = spell.dup
const baseName = Model.alias
const allAttrs = { [baseName]: [] }
const selection = { [baseName]: new Set() }
function appendColumn(node) {
if (node.type == 'id' && node.qualifiers && node.qualifiers.length > 0) {
const { qualifiers: [qualifier], value } = node
if (Array.isArray(allAttrs[qualifier]) && (qualifier == baseName || qualifier in joins)) {
allAttrs[qualifier].push(value)
for (const name of attributes) {
const { type, qualifiers, value } = parseExpr(name)
if (type == 'id') {
if (qualifiers && qualifiers[0] != baseName) {
const [qualifier] = qualifiers
if (!selection[qualifier]) selection[qualifier] = new Set()
selection[qualifier].add(value)
} else {
selection[baseName].add(value)
}

@@ -261,21 +277,38 @@ }

for (const qualifier in joins) {
const { on, attributes: select } = joins[qualifier]
if (select) {
allAttrs[qualifier] = select
} else {
allAttrs[qualifier] = '*'
const relation = joins[qualifier]
if (!selection[qualifier]) {
selection[qualifier] = relation.attributes || new Set()
}
walkExpr(on, appendColumn)
if (selection[qualifier].size > 0) selection[qualifier].add(relation.Model.primaryKey)
walkExpr(relation.on, ({ type, qualifiers, value }) => {
if (type == 'id' && qualifiers && qualifiers[0] == baseName) {
const attrs = selection[qualifiers[0]]
if (attrs.size > 0) attrs.add(value)
}
})
}
if (spell.attributes.size > 0) {
for (const name of allAttrs[baseName]) spell.attributes.add(name)
spell.attributes.add(Model.primaryKey)
subspell.attributes = selection[baseName]
selection[baseName] = new Set()
subspell.whereConditions = []
for (let i = whereConditions.length - 1; i >= 0; i--) {
const condition = whereConditions[i]
let internal = true
walkExpr(condition, ({ type, qualifiers }) => {
if (type == 'id' && qualifiers && qualifiers[0] != baseName) {
internal = false
}
})
if (internal) {
subspell.whereConditions.push(condition)
whereConditions.splice(i, 1)
}
}
allAttrs[baseName] = '*'
const columns = []
for (const qualifier in allAttrs) {
for (const qualifier in selection) {
const { Model: RefModel } = qualifier == baseName ? Model : joins[qualifier]
const attrs = allAttrs[qualifier]
if (attrs instanceof Set) {
const attrs = selection[qualifier]
if (attrs.size > 0) {
for (const name of attrs) {

@@ -293,10 +326,9 @@ columns.push(`${escapeId(qualifier)}.${escapeId(RefModel.unalias(name))}`)

if (attributes.size > 0 || whereConditions.length > 0 || groups.size > 0 || orders.length > 0 || offset > 0 || limit > 0) {
chunks.push(`FROM (${formatSelectWithoutJoin(spell)})`)
subspell.orders = []
if (subspell.attributes.size > 0 || subspell.whereConditions.length > 0 || skip > 0 || rowCount > 0) {
chunks.push(`FROM (${formatSelectWithoutJoin(subspell)}) AS ${escapeId(baseName)}`)
} else {
chunks.push(`FROM ${escapeId(Model.table)}`)
chunks.push(`FROM ${escapeId(Model.table)} AS ${escapeId(baseName)}`)
}
chunks.push(`AS ${escapeId(baseName)}`)
for (const qualifier in joins) {

@@ -319,2 +351,5 @@ const { Model: RefModel, on } = joins[qualifier]

if (whereConditions.length > 0) chunks.push(`WHERE ${formatConditions(spell, whereConditions)}`)
if (orders.length > 0) chunks.push(`ORDER BY ${formatOrders(spell).join(', ')}`)
return chunks.join(' ')

@@ -378,6 +413,3 @@ }

/**
* Taking advantage of MySQL's `on duplicate key update`, though knex does
* not support this because it has got several databases to be compliant of.
* PostgreSQL has got proper `upsert`. Maybe we can file a PR to knex
* someday.
* Taking advantage of MySQL's `on duplicate key update`, though knex does not support this because it has got several databases to be compliant of. PostgreSQL has got proper `upsert`. Maybe we can file a PR to knex someday.
*

@@ -448,5 +480,8 @@ * References:

} else {
if (relation.select && !relation.attributes) {
relation.attributes = new Set(Array.from(RefModel.attributes).filter(parseSelect(relation.select)))
}
const { select, throughRelation } = opts
const attributes = select
? new Set(Array.from(RefModel.attributes).filter(spell.constructor.parseSelect(select)))
? new Set(Array.from(RefModel.attributes).filter(parseSelect(select)))
: relation.attributes

@@ -465,53 +500,47 @@

function isCalculation(func) {
return ['count', 'average', 'minimun', 'maximum', 'sum'].includes(func )
}
/**
* If Model supports soft delete, and deletedAt isn't specified in whereConditions yet, and the table isn't a subquery, append a default where({ deletedAt: null }).
*/
function Spell_deletedAtIsNull() {
const { Model, table, whereConditions } = this
for (const token of whereConditions) {
const { type, value } = token.args[0]
if (type == 'id' && value == 'deletedAt') return this
}
if (table.type == 'id' && Model.schema.deletedAt) {
return this.$where({ deletedAt: null })
}
return this
}
class Spell {
constructor(Model, fn) {
this.promise = new Promise(resolve => {
setImmediate(() => {
if (!this.cancelled) resolve(fn(this.toSqlString()))
})
})
constructor(Model, factory, opts) {
this.Model = Model
this.command = 'select'
this.attributes = new Set()
this.table = parseExpr(Model.table)
this.whereConditions = []
this.groups = new Set()
this.orders = []
this.havingConditions = []
this.joins = {}
this.subqueryIndex = 0
this.scopes = [
/**
* If Model supports soft delete, and deletedAt isn't specified in whereConditions yet,
* and the table isn't a subquery, append a default where({ deletedAt: null }).
*/
function Spell_deletedAtIsNull() {
const { Model, table, whereConditions } = this
for (const ast of whereConditions) {
const { type, value } = ast.args[0]
if (type == 'id' && value == 'deletedAt') return
}
if (table.type == 'id' && Model.schema.deletedAt) {
this.where({ deletedAt: null })
}
}
]
this.factory = factory
this.triggered = false
Object.assign(this, {
command: 'select',
attributes: new Set(),
table: parseExpr(Model.table),
whereConditions: [],
groups: new Set(),
orders: [],
havingConditions: [],
joins: {},
scopes: [ Spell_deletedAtIsNull ],
subqueryIndex: 0
}, opts)
}
static parseSelect(select) {
const type = typeof select
if (type == 'function') return select
if (type == 'string') select = select.split(/\s+/)
if (Array.isArray(select)) {
return select.includes.bind(select)
} else {
throw new Error(`Invalid select ${select}`)
}
}
get dispatchable() {
const { attributes, Model } = this
const { attributes } = this
for (const name of attributes) {
if (!(name in Model.schema)) return false
const { type, name: func } = parseExpr(name)
if (type == 'func' && isCalculation(func)) return false
// currently `foo as bar` is parsed as `{ type: 'op', name: 'as', args: ['foo', 'bar'] }`
if (type == 'op') return false
}

@@ -522,10 +551,48 @@ return this.groups.size == 0

get unscoped() {
this.scopes = []
return this
const spell = this.dup
spell.scopes = []
return spell
}
get dup() {
return new Spell(this.Model, this.factory, {
command: this.command,
attributes: new Set(Array.from(this.attributes)),
values: this.values,
table: this.table,
whereConditions: [].concat(this.whereConditions),
groups: new Set(Array.from(this.groups)),
orders: [].concat(this.orders),
havingConditions: [].concat(this.havingConditions),
joins: this.joins,
skip: this.skip,
rowCount: this.rowCount,
scopes: [].concat(this.scopes),
triggered: false,
onresolve: this.onresolve
})
}
/**
* Fake this as a thenable object.
* @param {Function} resolve
* @param {Function} reject
*/
then(resolve, reject) {
return this.promise.then(resolve, reject)
// `Model.findOne()` needs to edit the resolved results before returning it to user. Hence a intermediate resolver called `onresolve` is added. It gets copied when the spell is duplicated too.
let promise = new Promise(resolve => {
setImmediate(() => {
const { factory } = this
resolve(factory(this.toSqlString()))
})
})
const { onresolve } = this
if (onresolve) promise = promise.then(onresolve)
return promise.then(resolve, reject)
}
/**
* Same as above
* @param {Function} reject
*/
catch(reject) {

@@ -535,3 +602,3 @@ return this.promise.catch(reject)

insert(values) {
$insert(values) {
this.values = values

@@ -542,3 +609,3 @@ this.command = 'insert'

upsert(values) {
$upsert(values) {
this.values = values

@@ -549,3 +616,3 @@ this.command = 'upsert'

select(...names) {
$select(...names) {
if (names.length == 1) names = names[0].split(/\s+/)

@@ -556,3 +623,3 @@ for (const name of names) this.attributes.add(name)

update(values) {
$update(values) {
this.values = values

@@ -563,3 +630,3 @@ this.command = 'update'

delete() {
$delete() {
this.command = 'delete'

@@ -569,13 +636,10 @@ return this

from(table) {
if (table instanceof Spell) {
table.cancelled = true
this.table = { type: 'array', value: table }
} else {
this.table = parseExpr(table)
}
$from(table) {
this.table = table instanceof Spell
? { type: 'array', value: table }
: parseExpr(table)
return this
}
where(conditions, ...values) {
$where(conditions, ...values) {
this.whereConditions.push(...parseConditions(conditions, ...values))

@@ -585,3 +649,3 @@ return this

group(...names) {
$group(...names) {
for (const name of names) this.groups.add(name)

@@ -591,26 +655,34 @@ return this

order(name, order) {
if (!order) {
$order(name, order) {
if (typeof name == 'object') {
for (const prop in name) {
this.$order(prop, name[prop] || 'asc')
}
}
else if (!order) {
[name, order] = name.split(/\s+/)
this.$order(name, order || 'asc')
}
if (!isIdentifier(name)) throw new Error(`Invalid order attribute ${name}`)
this.orders.push([name, order.toLowerCase() == 'desc' ? 'desc' : 'asc'])
else {
this.orders.push([name, order && order.toLowerCase() == 'desc' ? 'desc' : 'asc'])
}
return this
}
offset(offset) {
offset = +offset
if (Number.isNaN(offset)) throw new Error(`Invalid offset ${offset}`)
this.offset = offset
$offset(skip) {
skip = +skip
if (Number.isNaN(skip)) throw new Error(`Invalid offset ${skip}`)
this.skip = skip
return this
}
limit(limit) {
limit = +limit
if (Number.isNaN(limit)) throw new Error(`Invalid limit ${limit}`)
this.limit = limit
$limit(rowCount) {
rowCount = +rowCount
if (Number.isNaN(rowCount)) throw new Error(`Invalid limit ${rowCount}`)
this.rowCount = rowCount
return this
}
having(conditions, values) {
$having(conditions, values) {
this.havingConditions.push(...parseConditions(conditions, values))

@@ -620,3 +692,3 @@ return this

count(name = '*') {
$count(name = '*') {
name = isIdentifier(name) ? name : '*'

@@ -633,3 +705,3 @@ this.attributes = new Set([`count(${name}) as count`])

*/
with(...names) {
$with(...names) {
for (const name of names) {

@@ -651,3 +723,3 @@ if (typeof name == 'object') {

* Post.join(User, 'users.id = posts.authorId')
* Post.join(TagMap, 'tagMaps.targetId = posts.id and tagMaps.targetType = ?', 0)
* Post.join(TagMap, 'tagMaps.targetId = posts.id and tagMaps.targetType = 0')
*

@@ -658,15 +730,13 @@ * @param {Model} Model

*/
join(Model, onConditions, ...values) {
$join(Model, onConditions, ...values) {
if (typeof Model == 'string') {
return this.with(...arguments)
return this.$with(...arguments)
}
const qualifier = Model.alias
const { joins } = this
const prop = Model.alias
if (prop in this.joins) {
throw new Error(`Invalid join target. ${prop} already defined.`)
if (qualifier in joins) {
throw new Error(`Invalid join target. ${qualifier} already defined.`)
}
this.joins[prop] = {
Model,
on: parseConditions(onConditions, ...values)[0]
}
joins[qualifier] = { Model, on: parseConditions(onConditions, ...values)[0] }

@@ -678,2 +748,3 @@ return this

for (const scope of this.scopes) scope.call(this)
switch (this.command) {

@@ -696,3 +767,2 @@ case 'insert':

toString() {
this.cancelled = true
return this.toSqlString()

@@ -702,2 +772,15 @@ }

for (const method of Object.getOwnPropertyNames(Spell.prototype)) {
if (method[0] == '$') {
const descriptor = Object.getOwnPropertyDescriptor(Spell.prototype, method)
Object.defineProperty(Spell.prototype, method.slice(1), Object.assign({}, descriptor, {
value: function() {
const spell = this.dup
spell[method](...arguments)
return spell
}
}))
}
}
module.exports = Spell
{
"name": "leoric",
"version": "0.1.0",
"version": "0.1.1",
"description": "Object-relational mapping for Node.js",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -7,2 +7,2 @@ # Leoric

Leoric is an object-relational mapping for Node.js, which is heavily influenced by Active Record.
Leoric is an object-relational mapping for Node.js, which is heavily influenced by Active Record. See the [documentation](http://cyj.me/leoric) for detail.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc