src/intern.ts
export class intern {
static is_new(ent: any): boolean {
// NOTE: This function is intended for use by the #save method. This
// function returns true when the entity argument is assumed to not yet
// exist in the store.
//
// In terms of code, if client code looks like so:
// ```
// seneca.make('product')
// .data$({ label, price })
// .save$(done)
// ```
//
// - `is_new` will be invoked from the #save method and return
// true, because the product entity is yet to be saved.
//
// The following client code will cause `is_new` to return false,
// when invoked from the #save method, because the user entity already
// exists:
// ```
// seneca.make('user')
// .load$(user_id, (err, user) => {
// if (err) return done(err)
//
// return user
// .data$({ email, username })
// .save$(done)
// })
// ```
//
return null != ent && null == ent.id
}
static is_upsert(msg: any): boolean {
const { ent, q } = msg
return intern.is_new(ent) && q && Array.isArray(q.upsert$)
}
static find_mement(entmap: any, base_ent: any, filter: any): any {
const { base, name } = base_ent.canon$({ object: true })
const entset = entmap[base] && entmap[base][name]
if (null == entset) {
return null
}
let out = null
for (const ent_id in entset) {
const mement = entset[ent_id]
if (matches(mement, filter)) {
out = mement
break
}
}
return out
function matches(ent: any, filter: any): boolean {
for (const fp in filter) {
if (fp in ent && filter[fp] === ent[fp]) {
continue
}
return false
}
return true
}
}
static update_mement(
entmap: any,
base_ent: any,
filter: any,
new_attrs: any,
) {
const ent_to_update = intern.find_mement(entmap, base_ent, filter)
if (ent_to_update) {
Object.assign(ent_to_update, new_attrs)
return ent_to_update
}
return null
}
static should_merge(ent: any, plugin_opts: any): boolean {
return !(false === plugin_opts.merge || false === ent.merge$)
}
// NOTE: Seneca supports a reasonable set of features
// in terms of listing. This function can handle
// sorting, skiping, limiting and general retrieval.
//
static listents(seneca: any, entmap: any, qent: any, q: any, done: any) {
let list = []
let canon = qent.canon$({ object: true })
let base = canon.base
let name = canon.name
let entset = entmap[base] ? entmap[base][name] : null
let ent
if (null != entset && null != q) {
if ('string' == typeof q) {
ent = entset[q]
if (ent) {
list.push(ent)
}
} else if (Array.isArray(q)) {
q.forEach(function (id) {
let ent = entset[id]
if (ent) {
ent = qent.make$(ent)
list.push(ent)
}
})
} else if ('object' === typeof q) {
let entids = Object.keys(entset)
next_ent: for (let id of entids) {
ent = entset[id]
for (let p in q) {
let qv = q[p] // query val
let ev = ent[p] // ent val
if (-1 === p.indexOf('$')) {
if (Array.isArray(qv)) {
if (-1 === qv.indexOf(ev)) {
continue next_ent
}
} else if (intern.is_object(qv)) {
// mongo style constraints
if (
(null != qv.$ne && qv.$ne == ev) ||
(null != qv.$gte && qv.$gte > ev) ||
(null != qv.$gt && qv.$gt >= ev) ||
(null != qv.$lt && qv.$lt <= ev) ||
(null != qv.$lte && qv.$lte < ev) ||
(null != qv.$in && -1 === qv.$in.indexOf(ev)) ||
(null != qv.$nin && -1 !== qv.$nin.indexOf(ev)) ||
false
) {
continue next_ent
}
} else {
if (intern.is_date(qv)) {
if (!(intern.is_date(ev) && intern.eq_dates(qv, ev))) {
continue next_ent
}
} else if (qv !== ev) {
continue next_ent
}
}
}
}
ent = qent.make$(ent)
list.push(ent)
}
}
}
// Always sort first, this is the 'expected' behaviour.
if (null != q && q.sort$) {
let sf: any
for (sf in q.sort$) {
break
}
let sd = q.sort$[sf] < 0 ? -1 : 1
list = list.sort(function (a, b) {
return sd * (a[sf] < b[sf] ? -1 : a[sf] === b[sf] ? 0 : 1)
})
}
// Skip before limiting.
if (null != q && q.skip$ && q.skip$ > 0) {
list = list.slice(q.skip$)
}
// Limited the possibly sorted and skipped list.
if (null != q && q.limit$ && q.limit$ >= 0) {
list = list.slice(0, q.limit$)
}
// Prune fields
if (null != q && q.fields$) {
for (let i = 0; i < list.length; i++) {
let entfields = list[i].fields$()
for (let j = 0; j < entfields.length; j++) {
if ('id' !== entfields[j] && -1 == q.fields$.indexOf(entfields[j])) {
delete list[i][entfields[j]]
}
}
}
}
// Return the resulting list to the caller.
done.call(seneca, null, list)
}
static clean_array(ary: string[]): string[] {
return ary.filter((prop: string) => !prop.includes('$'))
}
static is_object(x: any): boolean {
return '[object Object]' === toString.call(x)
}
static is_date(x: any): boolean {
return '[object Date]' === toString.call(x)
}
static eq_dates(lv: Date, rv: Date): boolean {
return lv.getTime() === rv.getTime()
}
}