memory-orm
Version:
client side ORM + map reduce
302 lines (301 loc) • 8.12 kB
JavaScript
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
exports.Rule = void 0
const tslib_1 = require('tslib')
const get_1 = tslib_1.__importDefault(require('lodash/get'))
const uniq_1 = tslib_1.__importDefault(require('lodash/uniq'))
const property_1 = tslib_1.__importDefault(require('lodash/property'))
const snakeCase_1 = tslib_1.__importDefault(require('lodash/snakeCase'))
const Mem = tslib_1.__importStar(require('./userdata'))
const model_1 = require('./model')
const list_1 = require('./list')
const set_1 = require('./set')
const map_1 = require('./map')
const query_1 = require('./query')
const finder_1 = require('./finder')
const mem_1 = require('./mem')
function rename(base) {
base = snakeCase_1.default(base).replace(/s$/, '')
const name = Mem.Name[base]
if (name) {
return name
}
const list = `${base}s`
const o = (Mem.Name[list] = Mem.Name[base] = mem_1.PureObject())
o.base = base
o.list = list
o.id = `${base}_id`
o.ids = `${base}_ids`
o.relations = []
o.deploys = []
o.depends = []
return o
}
function method({ prototype }, key, o) {
Object.defineProperty(prototype, key, o)
}
class Rule {
constructor(
base,
{
model = class model extends model_1.Model {},
list = class list extends list_1.List {},
set = class set extends set_1.Set {},
map = class map extends map_1.Map {},
scope,
scope_without_cache,
schema,
deploy,
} = {}
) {
this.$name = rename(base)
this.state = mem_1.State.base(this.$name.list)
this.all = query_1.Query.build(this.state)
this.all.$sort['_reduce.list'] = {}
this.all._cache = {}
this.all._finder = new finder_1.Finder()
this.depend_on(this.$name.list)
this.model = model
this.list = list
this.set = set
this.map = map
if (scope_without_cache) {
this.scope_without_cache(scope_without_cache)
}
if (scope) {
this.scope(scope)
}
if (deploy) {
this.deploy(deploy)
}
if (schema) {
this.schema(schema)
}
}
schema(cb) {
cb.call(this)
this.model.$name = this.list.$name = this.set.$name = this.map.$name = this.$name
this.all._finder.join(this)
Mem.Set[this.$name.base] = new this.set(this)
Mem.Query[this.$name.list] = this.all
Mem.Finder[this.$name.list] = this.all._finder
return this
}
key_by(keys) {
const get = (() => {
if (undefined === keys) {
return function () {
return this._id
}
}
if (keys instanceof Function) {
return keys
}
if (keys instanceof String) {
return property_1.default(keys)
}
if (keys instanceof Array) {
return property_1.default(keys)
}
throw new Error(`unimplemented ${keys}`)
})()
method(this.model, 'id', {
enumerable: true,
get,
})
}
deploy(cb) {
this.$name.deploys.push(cb)
}
depend_on(parent) {
Mem.Name[parent].depends.push(parent)
}
scope_without_cache(cb) {
const cmd = cb(this.all)
for (const key in cmd) {
this.all[key] = cmd[key]
}
}
scope(cb) {
const cmd = cb(this.all)
for (const key in cmd) {
this.use_cache(key, cmd[key])
}
}
property(type, o) {
Object.defineProperties(this[type].prototype, o)
}
default_scope(scope) {
this.all._copy(scope(this.all))
const base = mem_1.State.base(this.$name.list)
base.$sort = this.all.$sort
}
shuffle() {
this.default_scope((all) => all.shuffle())
}
sort(...sort) {
this.default_scope((all) => all.sort(...sort))
}
order(keys, order) {
this.default_scope((all) => all.order(keys, order))
}
relation_to_one(key, target, ik, else_id) {
this.$name.relations.push(key)
method(this.model, key, {
enumerable: true,
get() {
const id = get_1.default(this, ik)
return Mem.Query[target].find(id, else_id)
},
})
}
relation_to_many(key, target, ik, cmd, qk) {
const { all } = this
this.use_cache(key, (id) => Mem.Query[target].distinct(false)[cmd]({ [qk]: id }))
this.$name.relations.push(key)
method(this.model, key, {
enumerable: true,
get() {
return all[key](this[ik])
},
})
}
relation_tree(key, ik) {
const { all } = this
this.use_cache(key, (id, n) => {
if (n) {
const q = all.where({ [ik]: id })
return all[key](q.ids, n - 1)
} else {
return all.where({ id })
}
})
this.$name.relations.push(key)
method(this.model, key, {
enumerable: true,
value(n) {
return all[key]([this.id], n)
},
})
}
relation_graph(key, ik) {
const { all } = this
this.use_cache(key, (id, n) => {
const q = all.where({ id })
if (n) {
const ids = []
for (const a of q.pluck(ik)) {
if (a != null) {
for (let k of a) {
if (k != null) {
ids.push(k)
}
}
}
}
return all[key](uniq_1.default(ids), n - 1)
} else {
return q
}
})
this.$name.relations.push(key)
method(this.model, key, {
enumerable: true,
value(n) {
return all[key]([this.id], n)
},
})
}
use_cache(key, val) {
if (val instanceof Function) {
this.all[key] = (...args) => {
const name = `${key}:${JSON.stringify(args)}`
return this.all._cache[name] || (this.all._cache[name] = val(...args))
}
} else {
this.all[key] = val
}
}
path(keys, option = { key: 'id' }) {
const { key } = option
const { base, list } = this.$name
let tail_key = keys[keys.length - 1]
if ('*' === tail_key) {
this.belongs_to(base)
this.has_many(list)
keys.pop()
tail_key = keys[keys.length - 1]
}
for (const key of keys) {
this.belongs_to(key)
}
this.deploy(({ o, reduce }) => {
if ('string' !== typeof o[key]) {
throw new Error(`id [${o[key]}] must be string.`)
}
const subids = o[key].split('-')
o[`${key}x`] = subids[subids.length - 1]
for (let idx = 0; idx < keys.length; idx++) {
const sub_key = keys[idx]
o[`${sub_key}_id`] = subids.slice(0, idx + 1).join('-')
}
// '*' support.
if (base && keys.length + 1 < subids.length) {
o[`${base}_id`] = subids.slice(0, -1).join('-')
}
reduce(key, { navi: subids })
})
const { all } = this
const pk = `${tail_key}_id`
method(this.model, 'siblings', {
get() {
return all.where({ [pk]: this[pk] })
},
})
}
belongs_to(to, option = {}) {
const name = rename(to)
const { key = name.id, target = name.list, miss } = option
this.relation_to_one(name.base, target, key, miss)
}
habtm(to, option = {}) {
const name = rename(to)
if (option.reverse) {
const { key = this.$name.ids, target = to } = option
this.relation_to_many(name.list, target, 'id', 'in', key)
} else {
const { key = name.ids, target = name.list } = option
this.relation_to_many(name.list, target, key, 'where', 'id')
}
}
has_many(to, option = {}) {
const name = rename(to)
const { key = this.$name.id, target = name.list } = option
this.relation_to_many(name.list, target, 'id', 'where', key)
}
tree(option = {}) {
const fk = this.$name.id
this.relation_tree('nodes', fk)
this.belongs_to(this.$name.base, option)
Object.defineProperties(this.all, {
leaf: {
get() {
const not_leaf = uniq_1.default(this.pluck(fk))
return this.where((o) => !not_leaf.includes(o.id))
},
},
})
}
graph(option = {}) {
const { directed, cost } = option
const ik = this.$name.ids
this.relation_to_many(this.$name.list, this.$name.list, ik, 'where', 'id')
this.relation_graph('path', ik)
if (!directed) {
return true // todo
}
return false
}
}
exports.Rule = Rule
//# sourceMappingURL=rule.js.map