dobo 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +0 -0
- package/.github/workflows/repo-lockdown.yml +0 -0
- package/.jsdoc.conf.json +0 -0
- package/LICENSE +0 -0
- package/README.md +2 -2
- package/docs/Dobo.html +0 -0
- package/docs/data/search.json +0 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/global.html +0 -0
- package/docs/index.html +0 -0
- package/docs/index.js.html +0 -0
- package/docs/lib_collect-connections.js.html +0 -0
- package/docs/lib_collect-drivers.js.html +0 -0
- package/docs/lib_collect-features.js.html +0 -0
- package/docs/lib_collect-schemas.js.html +0 -0
- package/docs/lib_index.js.html +0 -0
- package/docs/method_model_create.js.html +0 -0
- package/docs/method_model_drop.js.html +0 -0
- package/docs/method_model_exists.js.html +0 -0
- package/docs/method_record_count.js.html +0 -0
- package/docs/method_record_create.js.html +0 -0
- package/docs/method_record_find-all.js.html +0 -0
- package/docs/method_record_find-one.js.html +0 -0
- package/docs/method_record_find.js.html +0 -0
- package/docs/method_record_get.js.html +0 -0
- package/docs/method_record_remove.js.html +0 -0
- package/docs/method_record_update.js.html +0 -0
- package/docs/method_record_upsert.js.html +0 -0
- package/docs/method_sanitize_body.js.html +0 -0
- package/docs/method_sanitize_date.js.html +0 -0
- package/docs/method_sanitize_id.js.html +0 -0
- package/docs/method_validate.js.html +0 -0
- package/docs/module-Lib.html +0 -0
- package/docs/scripts/core.js +476 -477
- package/docs/scripts/core.min.js +0 -0
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/search.min.js +0 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +0 -0
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +282 -285
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1195 -1202
- package/docs/scripts/third-party/hljs.js +1 -1
- package/docs/scripts/third-party/popper.js +1 -1
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +508 -509
- package/docs/scripts/third-party/tocbot.min.js +0 -0
- package/docs/static/bitcoin.jpeg +0 -0
- package/docs/static/home.md +0 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +0 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +0 -0
- package/docs/styles/clean-jsdoc-theme-light.css +0 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -0
- package/docs/styles/clean-jsdoc-theme.min.css +0 -0
- package/extend/bajo/intl/en-US.json +66 -28
- package/extend/bajo/intl/id.json +55 -27
- package/extend/bajoCli/applet/clear-record.js +22 -0
- package/extend/bajoCli/applet/connection.js +0 -0
- package/extend/bajoCli/applet/count-record.js +27 -0
- package/extend/bajoCli/applet/create-aggregate.js +33 -0
- package/extend/bajoCli/applet/create-histogram.js +33 -0
- package/extend/bajoCli/applet/create-record.js +39 -0
- package/extend/bajoCli/applet/find-record.js +27 -0
- package/extend/bajoCli/applet/get-record.js +27 -0
- package/extend/bajoCli/applet/lib/post-process.js +10 -17
- package/extend/bajoCli/applet/model.js +22 -0
- package/extend/bajoCli/applet/rebuild-model.js +91 -0
- package/extend/bajoCli/applet/remove-record.js +27 -0
- package/extend/bajoCli/applet/update-record.js +44 -0
- package/extend/bajoCli/applet.js +0 -0
- package/extend/dobo/driver/memory.js +170 -0
- package/extend/dobo/feature/created-at.js +9 -7
- package/extend/dobo/feature/dt.js +0 -0
- package/extend/dobo/feature/immutable.js +30 -0
- package/extend/dobo/feature/int-id.js +0 -0
- package/extend/dobo/feature/removed-at.js +32 -54
- package/extend/dobo/feature/updated-at.js +14 -12
- package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +2 -6
- package/extend/waibuStatic/virtual.json +0 -0
- package/index.js +291 -366
- package/lib/collect-connections.js +49 -21
- package/lib/collect-drivers.js +19 -33
- package/lib/collect-features.js +24 -17
- package/lib/collect-models.js +319 -0
- package/lib/factory/action.js +161 -0
- package/lib/factory/connection.js +62 -0
- package/lib/factory/driver.js +358 -0
- package/lib/factory/feature.js +33 -0
- package/lib/factory/model/_util.js +402 -0
- package/lib/factory/model/build.js +15 -0
- package/lib/factory/model/clear-record.js +17 -0
- package/lib/factory/model/count-record.js +17 -0
- package/lib/factory/model/create-aggregate.js +17 -0
- package/lib/factory/model/create-attachment.js +29 -0
- package/lib/factory/model/create-histogram.js +17 -0
- package/lib/factory/model/create-record.js +35 -0
- package/lib/factory/model/drop.js +15 -0
- package/lib/factory/model/exists.js +21 -0
- package/lib/factory/model/find-all-record.js +71 -0
- package/lib/factory/model/find-attachment.js +29 -0
- package/lib/factory/model/find-one-record.js +19 -0
- package/{method/record/find.js → lib/factory/model/find-record.js} +103 -115
- package/lib/factory/model/get-attachment.js +15 -0
- package/lib/factory/model/get-record.js +79 -0
- package/lib/factory/model/list-attachment.js +37 -0
- package/lib/{add-fixtures.js → factory/model/load-fixtures.js} +69 -67
- package/lib/factory/model/remove-attachment.js +15 -0
- package/lib/factory/model/remove-record.js +59 -0
- package/lib/factory/model/sanitize-body.js +62 -0
- package/lib/factory/model/sanitize-id.js +7 -0
- package/lib/factory/model/sanitize-record.js +26 -0
- package/lib/factory/model/update-attachment.js +9 -0
- package/lib/factory/model/update-record.js +81 -0
- package/lib/factory/model/upsert-record.js +95 -0
- package/{method → lib/factory/model}/validate.js +38 -52
- package/lib/factory/model.js +150 -0
- package/lib/index.js +0 -0
- package/package.json +8 -4
- package/wiki/APPLETS.md +0 -0
- package/wiki/CHANGES.md +46 -0
- package/wiki/CONFIG.md +0 -0
- package/wiki/CONTRIBUTING.md +0 -0
- package/wiki/DEV-GUIDE.md +0 -0
- package/wiki/ECOSYSTEM.md +0 -0
- package/wiki/GETTING-STARTED.md +10 -10
- package/wiki/QUERY-LANGUAGE.md +0 -0
- package/wiki/USER-GUIDE.md +0 -0
- package/extend/bajoCli/applet/model-clear.js +0 -11
- package/extend/bajoCli/applet/model-rebuild.js +0 -101
- package/extend/bajoCli/applet/record-create.js +0 -43
- package/extend/bajoCli/applet/record-find.js +0 -28
- package/extend/bajoCli/applet/record-get.js +0 -24
- package/extend/bajoCli/applet/record-remove.js +0 -24
- package/extend/bajoCli/applet/record-update.js +0 -47
- package/extend/bajoCli/applet/schema.js +0 -22
- package/extend/bajoCli/applet/stat-count.js +0 -24
- package/lib/build-bulk-action.js +0 -12
- package/lib/check-unique.js +0 -39
- package/lib/collect-schemas.js +0 -91
- package/lib/exec-feature-hook.js +0 -13
- package/lib/exec-validation.js +0 -21
- package/lib/generic-prop-sanitizer.js +0 -32
- package/lib/handle-attachment-upload.js +0 -16
- package/lib/mem-db/conn-sanitizer.js +0 -8
- package/lib/mem-db/instantiate.js +0 -41
- package/lib/mem-db/method/model/clear.js +0 -6
- package/lib/mem-db/method/model/create.js +0 -5
- package/lib/mem-db/method/model/drop.js +0 -5
- package/lib/mem-db/method/model/exists.js +0 -5
- package/lib/mem-db/method/record/create.js +0 -12
- package/lib/mem-db/method/record/find.js +0 -20
- package/lib/mem-db/method/record/get.js +0 -9
- package/lib/mem-db/method/record/remove.js +0 -13
- package/lib/mem-db/method/record/update.js +0 -15
- package/lib/mem-db/method/stat/count.js +0 -11
- package/lib/mem-db/start.js +0 -25
- package/lib/merge-attachment-info.js +0 -16
- package/lib/multi-rel-rows.js +0 -42
- package/lib/resolve-method.js +0 -16
- package/lib/sanitize-schema.js +0 -198
- package/lib/single-rel-rows.js +0 -38
- package/method/attachment/copy-uploaded.js +0 -34
- package/method/attachment/create.js +0 -29
- package/method/attachment/find.js +0 -27
- package/method/attachment/get-path.js +0 -12
- package/method/attachment/get.js +0 -12
- package/method/attachment/pre-check.js +0 -9
- package/method/attachment/remove.js +0 -11
- package/method/attachment/update.js +0 -7
- package/method/bulk/create.js +0 -46
- package/method/model/clear.js +0 -22
- package/method/model/create.js +0 -32
- package/method/model/drop.js +0 -31
- package/method/model/exists.js +0 -37
- package/method/record/clear.js +0 -24
- package/method/record/count.js +0 -66
- package/method/record/create.js +0 -111
- package/method/record/find-all.js +0 -41
- package/method/record/find-one.js +0 -70
- package/method/record/get.js +0 -89
- package/method/record/remove.js +0 -72
- package/method/record/update.js +0 -104
- package/method/record/upsert.js +0 -51
- package/method/sanitize/body.js +0 -85
- package/method/sanitize/date.js +0 -27
- package/method/sanitize/id.js +0 -17
- package/method/stat/aggregate.js +0 -23
- package/method/stat/histogram.js +0 -26
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const indexTypes = ['default', 'unique', 'primary', 'fulltext']
|
|
2
|
-
|
|
3
|
-
async function genericPropSanitizer ({ prop, schema, driver }) {
|
|
4
|
-
const { join } = this.app.bajo
|
|
5
|
-
const { has, get, each } = this.app.lib._
|
|
6
|
-
const { propType } = this.app.pluginClass.dobo
|
|
7
|
-
const def = propType[prop.type]
|
|
8
|
-
// detect from drivers
|
|
9
|
-
if (prop.type === 'string') {
|
|
10
|
-
def.minLength = prop.minLength ?? 0
|
|
11
|
-
def.maxLength = prop.maxLength ?? 255
|
|
12
|
-
if (has(prop, 'length')) def.maxLength = prop.length
|
|
13
|
-
if (prop.required && def.minLength === 0) def.minLength = 1
|
|
14
|
-
if (def.minLength > 0) prop.required = true
|
|
15
|
-
}
|
|
16
|
-
if (prop.autoInc && !['smallint', 'integer'].includes(prop.type)) delete prop.autoInc
|
|
17
|
-
each(['minLength', 'maxLength', 'textType'], p => {
|
|
18
|
-
if (!has(def, p)) {
|
|
19
|
-
delete prop[p]
|
|
20
|
-
return undefined
|
|
21
|
-
}
|
|
22
|
-
prop[p] = get(prop, p, get(this.config, `default.property.${prop.type}.${p}`, def[p]))
|
|
23
|
-
if (def.values && !def.values.includes(prop[p])) {
|
|
24
|
-
this.fatal('unsupportedAllowedChoices%s%s%s%s%s', p, prop[p], prop.name, schema.name, join(def.values))
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
if (prop.index && !indexTypes.includes(prop.index.type)) {
|
|
28
|
-
this.fatal('unsupportedIndexType%s%s%s%s', prop.index.type, prop.name, schema.name, join(indexTypes))
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default genericPropSanitizer
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
async function handleAttachmentUpload ({ action, name, id, options = {} } = {}) {
|
|
2
|
-
const { getPluginDataDir } = this.app.bajo
|
|
3
|
-
const { fs } = this.app.lib
|
|
4
|
-
const { req, mimeType, stats, setFile, setField } = options
|
|
5
|
-
|
|
6
|
-
name = this.attachmentPreCheck(name)
|
|
7
|
-
if (!name) return
|
|
8
|
-
if (action === 'remove') {
|
|
9
|
-
const dir = `${getPluginDataDir(this.ns)}/attachment/${name}/${id}`
|
|
10
|
-
await fs.remove(dir)
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
return this.attachmentCopyUploaded(name, id, { req, mimeType, stats, setFile, setField })
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default handleAttachmentUpload
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
let saving = false
|
|
2
|
-
|
|
3
|
-
async function instantiate ({ connection, schemas, noRebuild }) {
|
|
4
|
-
const { getPluginDataDir } = this.app.bajo
|
|
5
|
-
const { fs } = this.app.lib
|
|
6
|
-
const { pick } = this.app.lib._
|
|
7
|
-
this.memDb = this.memDb ?? {}
|
|
8
|
-
this.memDb.storage = this.memDb.storage ?? {}
|
|
9
|
-
this.memDb.instances = this.memDb.instances ?? []
|
|
10
|
-
const instance = pick(connection, ['name', 'type'])
|
|
11
|
-
this.memDb.instances.push(instance)
|
|
12
|
-
// if (noRebuild) return
|
|
13
|
-
const pdir = `${getPluginDataDir(this.ns)}/memDb/data` // persistence dir
|
|
14
|
-
fs.ensureDirSync(pdir)
|
|
15
|
-
const persistence = []
|
|
16
|
-
for (const schema of schemas) {
|
|
17
|
-
this.memDb.storage[schema.name] = this.memDb.storage[schema.name] ?? [] // init empty model
|
|
18
|
-
if (!schema.persistence) continue
|
|
19
|
-
persistence.push(schema.name)
|
|
20
|
-
// load if persistence
|
|
21
|
-
const file = `${pdir}/${schema.name}.json`
|
|
22
|
-
if (!fs.existsSync(file)) continue
|
|
23
|
-
try {
|
|
24
|
-
const data = fs.readFileSync(file, 'utf8')
|
|
25
|
-
this.memDb.storage[schema.name] = JSON.parse(data)
|
|
26
|
-
} catch (err) {
|
|
27
|
-
this.fatal('cantLoad%s%s', schema.name, err.message)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
setInterval(() => {
|
|
31
|
-
if (saving) return
|
|
32
|
-
saving = true
|
|
33
|
-
for (const item of persistence) {
|
|
34
|
-
const data = this.memDb.storage[item]
|
|
35
|
-
fs.writeFileSync(`${pdir}/${item}.json`, JSON.stringify(data), 'utf8')
|
|
36
|
-
}
|
|
37
|
-
saving = false
|
|
38
|
-
}, this.config.memDb.persistence.syncPeriodDur)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default instantiate
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import getRecord from './get.js'
|
|
2
|
-
|
|
3
|
-
async function create ({ schema, body, options = {} }) {
|
|
4
|
-
const { noResult } = options
|
|
5
|
-
const exist = await getRecord.call(this, { schema, id: body.id, options: { thrownNotFound: false } })
|
|
6
|
-
if (exist.data) throw this.error('recordExists%s%s', body.id, schema.name)
|
|
7
|
-
this.memDb.storage[schema.name].push(body)
|
|
8
|
-
if (noResult) return
|
|
9
|
-
return { data: body }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default create
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Query } from 'mingo'
|
|
2
|
-
|
|
3
|
-
async function find ({ schema, filter = {}, options = {} }) {
|
|
4
|
-
const { prepPagination } = this.app.dobo
|
|
5
|
-
const { omit } = this.app.lib._
|
|
6
|
-
const { limit, skip, sort, page } = await prepPagination(filter, schema)
|
|
7
|
-
const criteria = filter.query ?? {}
|
|
8
|
-
const q = new Query(criteria, { idKey: 'id' })
|
|
9
|
-
let cursor = q.find(this.memDb.storage[schema.name])
|
|
10
|
-
let count = 0
|
|
11
|
-
if (options.count && !options.dataOnly) count = cursor.count()
|
|
12
|
-
cursor = q.find(this.memDb.storage[schema.name])
|
|
13
|
-
if (sort) cursor.sort(sort)
|
|
14
|
-
if (!options.noLimit) cursor.skip(skip).limit(limit)
|
|
15
|
-
let result = { data: cursor.all(), page, limit, count, pages: Math.ceil(count / limit) }
|
|
16
|
-
if (!options.count) result = omit(result, ['count', 'pages'])
|
|
17
|
-
return result
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default find
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
async function get ({ schema, id, options = {} }) {
|
|
2
|
-
const { thrownNotFound = true } = options
|
|
3
|
-
const { find } = this.app.lib._
|
|
4
|
-
const result = find(this.memDb.storage[schema.name], { id })
|
|
5
|
-
if (!result && thrownNotFound) throw this.error('recordNotFound%s%s', id, schema.name, { statusCode: 404 })
|
|
6
|
-
return { data: result }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default get
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import getRecord from './get.js'
|
|
2
|
-
|
|
3
|
-
async function remove ({ schema, id, options = {} }) {
|
|
4
|
-
const { noResult } = options
|
|
5
|
-
const { findIndex, pullAt } = this.app.lib._
|
|
6
|
-
const rec = noResult ? undefined : await getRecord.call(this, { schema, id })
|
|
7
|
-
const idx = findIndex(this.memDb.storage[schema.name], { id })
|
|
8
|
-
pullAt(this.memDb.storage[schema.name], [idx])
|
|
9
|
-
if (noResult) return
|
|
10
|
-
return { oldData: rec.data }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default remove
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import getRecord from './get.js'
|
|
2
|
-
|
|
3
|
-
async function update ({ schema, id, body, options }) {
|
|
4
|
-
const { noResult } = options
|
|
5
|
-
const { findIndex, merge } = this.app.lib._
|
|
6
|
-
const old = noResult ? undefined : await getRecord.call(this, { schema, id })
|
|
7
|
-
const idx = findIndex(this.memDb.storage[schema.name], { id })
|
|
8
|
-
const current = this.memDb.storage[schema.name][idx]
|
|
9
|
-
this.memDb.storage[schema.name][idx] = merge(current, body)
|
|
10
|
-
if (noResult) return
|
|
11
|
-
const result = this.memDb.storage[schema.name][idx]
|
|
12
|
-
return { oldData: old.data, data: result }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default update
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Query } from 'mingo'
|
|
2
|
-
|
|
3
|
-
async function count ({ schema, filter = {}, options = {} }) {
|
|
4
|
-
const criteria = filter.query ?? {}
|
|
5
|
-
const q = new Query(criteria, { idKey: 'id' })
|
|
6
|
-
const cursor = q.find(this.memDb.storage[schema.name])
|
|
7
|
-
const count = cursor.count()
|
|
8
|
-
return { data: count }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default count
|
package/lib/mem-db/start.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import addFixtures from '../add-fixtures.js'
|
|
2
|
-
|
|
3
|
-
async function start () {
|
|
4
|
-
const { filter, map } = this.app.lib._
|
|
5
|
-
|
|
6
|
-
const conns = filter(this.connections, { type: 'dobo:memory' })
|
|
7
|
-
const schemas = filter(this.schemas, s => {
|
|
8
|
-
const names = map(conns, 'name')
|
|
9
|
-
return names.includes(s.connection)
|
|
10
|
-
})
|
|
11
|
-
if (schemas.length === 0) return
|
|
12
|
-
this.log.debug('addingFixtureToMemDb')
|
|
13
|
-
for (const schema of schemas) {
|
|
14
|
-
if (schema.persistence) {
|
|
15
|
-
this.log.warn('addingMemoryPersistenceIgnored', schema.name)
|
|
16
|
-
continue
|
|
17
|
-
}
|
|
18
|
-
let { success, error } = await addFixtures.call(this, schema.name)
|
|
19
|
-
success = success ?? 0
|
|
20
|
-
error = error ?? 0
|
|
21
|
-
this.log.trace('- %s@%s (%d/%d)', schema.name, schema.connection, success, success + error)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default start
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
async function mergeAttachmentInfo (rec, source, { mimeType, stats, fullPath }) {
|
|
2
|
-
const { importPkg } = this.app.bajo
|
|
3
|
-
const { fs } = this.app.lib
|
|
4
|
-
const { pick } = this.app.lib._
|
|
5
|
-
if (!this.app.waibu) return
|
|
6
|
-
const mime = await importPkg('waibu:mime')
|
|
7
|
-
|
|
8
|
-
if (mimeType) rec.mimeType = mime.getType(rec.file)
|
|
9
|
-
if (fullPath) rec.fullPath = source
|
|
10
|
-
if (stats) {
|
|
11
|
-
const s = fs.statSync(source)
|
|
12
|
-
rec.stats = pick(s, ['size', 'atime', 'ctime', 'mtime'])
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default mergeAttachmentInfo
|
package/lib/multi-rel-rows.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
async function multiRelRows ({ schema, records, options = {} }) {
|
|
2
|
-
const { isSet } = this.app.lib.aneka
|
|
3
|
-
const { uniq, find, map } = this.app.lib._
|
|
4
|
-
const props = schema.properties.filter(p => isSet(p.rel) && !(options.hidden ?? []).includes(p.name))
|
|
5
|
-
// const props = schema.properties.filter(p => isSet(p.rel))
|
|
6
|
-
options.rels = options.rels ?? []
|
|
7
|
-
if (props.length > 0) {
|
|
8
|
-
for (const prop of props) {
|
|
9
|
-
for (const key in prop.rel) {
|
|
10
|
-
if (!((typeof options.rels === 'string' && ['*', 'all'].includes(options.rels)) || options.rels.includes(key))) continue
|
|
11
|
-
const val = prop.rel[key]
|
|
12
|
-
if (val.fields.length === 0) continue
|
|
13
|
-
if (val.type !== 'one-to-one') continue
|
|
14
|
-
const relschema = this.getSchema(val.schema)
|
|
15
|
-
const matches = uniq(map(records, r => {
|
|
16
|
-
let v = r[prop.name]
|
|
17
|
-
if (val.propName === 'id') {
|
|
18
|
-
const idField = find(relschema.properties, { name: 'id' })
|
|
19
|
-
if (idField.type === 'integer') v = parseInt(v)
|
|
20
|
-
}
|
|
21
|
-
return v
|
|
22
|
-
}))
|
|
23
|
-
const query = {}
|
|
24
|
-
query[val.propName] = { $in: matches }
|
|
25
|
-
const relFilter = { query, limit: matches.length }
|
|
26
|
-
const relOptions = { dataOnly: true, rels: [] }
|
|
27
|
-
const results = await this.recordFind(relschema.name, relFilter, relOptions)
|
|
28
|
-
const fields = [...val.fields]
|
|
29
|
-
if (!fields.includes(prop.name)) fields.push(prop.name)
|
|
30
|
-
for (const i in records) {
|
|
31
|
-
records[i]._rel = records[i]._rel ?? {}
|
|
32
|
-
const rec = records[i]
|
|
33
|
-
const res = results.find(r => (r[val.propName] + '') === rec[prop.name] + '')
|
|
34
|
-
if (res) records[i]._rel[key] = await this.pickRecord({ record: res, fields, schema: relschema })
|
|
35
|
-
else records[i]._rel[key] = {}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default multiRelRows
|
package/lib/resolve-method.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
async function resolveMethod (name, method, options = {}) {
|
|
2
|
-
const { importModule } = this.app.bajo
|
|
3
|
-
const { fs } = this.app.lib
|
|
4
|
-
const { camelCase } = this.app.lib._
|
|
5
|
-
const { schema, driver, connection } = this.getInfo(name)
|
|
6
|
-
const [group, action] = method.split('-')
|
|
7
|
-
if (!options.force && (schema.disabled ?? []).includes(action)) throw this.error('methodIsDisabled%s%s', camelCase(method), name)
|
|
8
|
-
let file
|
|
9
|
-
if (connection.name === 'memory') file = `${this.app[driver.ns].dir.pkg}/lib/mem-db/method/${group}/${action}.js`
|
|
10
|
-
else file = `${this.app[driver.ns].dir.pkg}/extend/${this.ns}/method/${group}/${action}.js`
|
|
11
|
-
if (!fs.existsSync(file)) throw this.error('methodUnsupported%s%s', camelCase(method), name)
|
|
12
|
-
const handler = await importModule(file)
|
|
13
|
-
return { handler, schema, driver, connection }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default resolveMethod
|
package/lib/sanitize-schema.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import genericPropSanitizer from './generic-prop-sanitizer.js'
|
|
2
|
-
|
|
3
|
-
async function sanitizeFeature (item) {
|
|
4
|
-
const { get, isPlainObject, mergeWith, isArray } = this.app.lib._
|
|
5
|
-
for (const f of item.feature) {
|
|
6
|
-
const feature = get(this.feature, f.name) // source from collectFeature
|
|
7
|
-
if (!feature) this.fatal('unknownFeature%s%s', f.name, item.name)
|
|
8
|
-
let [ns, path] = f.name.split('.')
|
|
9
|
-
if (!path) ns = this.ns
|
|
10
|
-
const input = await feature.call(this.app[ns], f)
|
|
11
|
-
let props = input.properties
|
|
12
|
-
if (isPlainObject(props)) props = [props]
|
|
13
|
-
if (props.length > 0) item.properties.push(...props)
|
|
14
|
-
item.globalRules = item.globalRules ?? []
|
|
15
|
-
if (input.globalRules) {
|
|
16
|
-
item.globalRules = mergeWith(item.globalRules, input.globalRules, (oval, sval) => {
|
|
17
|
-
if (isArray(oval)) return oval.concat(sval)
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function sanitizeIndexes (item) {
|
|
24
|
-
const { generateId } = this.app.bajo
|
|
25
|
-
for (const idx of item.indexes) {
|
|
26
|
-
if (!idx.name) idx.name = `${(item.modelName ?? item.name).toUpperCase()}_${generateId()}`
|
|
27
|
-
if (!(typeof idx.fields === 'string' || Array.isArray(idx.fields))) this.fatal('onlyAcceptFields%s%s', 'indexes', item.name)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function sanitizeFullText (item) {
|
|
32
|
-
for (const f of item.fullText.fields ?? []) {
|
|
33
|
-
const prop = item.properties.find(p => p.name === f)
|
|
34
|
-
if (!prop) this.fatal('invalidFieldName%s%s', f, item.name)
|
|
35
|
-
// if (prop.type !== 'text') fatal.call(this, 'Fulltext index only available for field type \'text\' in \'%s@%s\'', f, item.name)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function sanitizeSchema (items) {
|
|
40
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
41
|
-
const { freeze, fatal, importModule, join, breakNsPath, runHook } = this.app.bajo
|
|
42
|
-
const { isEmpty, orderBy, map, keys, findIndex, find, each, isString, get, isPlainObject, camelCase, uniq, filter } = this.app.lib._
|
|
43
|
-
const { propType } = this.app.pluginClass.dobo
|
|
44
|
-
const propTypes = keys(propType)
|
|
45
|
-
const schemas = []
|
|
46
|
-
this.log.debug('loadingDbSchemas')
|
|
47
|
-
for (const k in items) {
|
|
48
|
-
this.log.trace('- %s (%d)', k, keys(items[k]).length)
|
|
49
|
-
for (const f in items[k]) {
|
|
50
|
-
const item = items[k][f]
|
|
51
|
-
item.cacheable = item.cacheable ?? true
|
|
52
|
-
await runHook(`dobo.${camelCase(item.name)}:beforeSanitizeSchema`, item)
|
|
53
|
-
const conn = find(this.connections, { name: item.connection })
|
|
54
|
-
if (!conn) fatal.call(this, 'Unknown connection \'%s@%s\'', item.name, item.connection)
|
|
55
|
-
item.fullText = item.fullText ?? { fields: [] }
|
|
56
|
-
item.indexes = item.indexes ?? []
|
|
57
|
-
let { ns, path: type } = breakNsPath(conn.type)
|
|
58
|
-
if (isEmpty(type)) type = conn.type
|
|
59
|
-
const driver = find(this.drivers, { type, ns, driver: conn.driver })
|
|
60
|
-
if (driver.lowerCaseModel) item.name = item.name.toLowerCase()
|
|
61
|
-
let file = `${ns}:/extend/${this.ns}/lib/${type}/prop-sanitizer.js`
|
|
62
|
-
let propSanitizer = await importModule(file)
|
|
63
|
-
if (!propSanitizer) propSanitizer = genericPropSanitizer
|
|
64
|
-
for (const idx in item.properties) {
|
|
65
|
-
let prop = item.properties[idx]
|
|
66
|
-
if (isString(prop)) {
|
|
67
|
-
let [name, type, maxLength, index, required] = prop.split(':')
|
|
68
|
-
if (!type) type = 'string'
|
|
69
|
-
maxLength = maxLength ?? 255
|
|
70
|
-
prop = { name, type }
|
|
71
|
-
if (type === 'string') prop.maxLength = parseInt(maxLength) || undefined
|
|
72
|
-
if (!isEmpty(index)) prop.index = { type: index === 'true' ? 'default' : index }
|
|
73
|
-
prop.required = required === 'true'
|
|
74
|
-
item.properties[idx] = prop
|
|
75
|
-
} else {
|
|
76
|
-
if (isString(prop.index)) prop.index = { type: prop.index }
|
|
77
|
-
else if (prop.index === true) prop.index = { type: 'default' }
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const idx = findIndex(item.properties, { name: 'id' })
|
|
81
|
-
if (idx === -1) item.properties.unshift(driver.idField)
|
|
82
|
-
item.feature = item.feature ?? []
|
|
83
|
-
await sanitizeFeature.call(this, item)
|
|
84
|
-
item.disabled = item.disabled ?? []
|
|
85
|
-
if (item.disabled === 'all') item.disabled = ['find', 'get', 'create', 'update', 'remove']
|
|
86
|
-
else if (item.disabled === 'readonly') item.disabled = ['create', 'update', 'remove']
|
|
87
|
-
for (const idx in item.properties) {
|
|
88
|
-
let prop = item.properties[idx]
|
|
89
|
-
if (!prop.type) {
|
|
90
|
-
prop.type = 'string'
|
|
91
|
-
prop.maxLength = 255
|
|
92
|
-
}
|
|
93
|
-
if (!propTypes.includes(prop.type)) {
|
|
94
|
-
let success = false
|
|
95
|
-
const feature = get(this.feature, isPlainObject(prop.type) ? prop.type.name : prop.type)
|
|
96
|
-
if (feature) {
|
|
97
|
-
let opts = { fieldName: prop.name }
|
|
98
|
-
if (isPlainObject(prop.type)) opts = defaultsDeep(opts, prop.type)
|
|
99
|
-
else opts.name = prop.type
|
|
100
|
-
const feat = find(item.feature, opts)
|
|
101
|
-
if (!feat) item.feature.push(opts)
|
|
102
|
-
const input = await feature.call(this, opts)
|
|
103
|
-
if (input.properties && input.properties.length > 0) {
|
|
104
|
-
prop = input.properties[0]
|
|
105
|
-
success = true
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (!success) fatal.call(this, 'Unsupported property type \'%s@%s\' in \'%s\'', prop.type, prop.name, item.name)
|
|
109
|
-
}
|
|
110
|
-
if (prop.index) {
|
|
111
|
-
if (prop.index === 'unique') prop.index = { type: 'unique' }
|
|
112
|
-
else if (prop.index === 'fulltext') prop.index = { type: 'fulltext' }
|
|
113
|
-
else if (prop.index === 'primary') prop.index = { type: 'primary' }
|
|
114
|
-
else if (prop.index === true) prop.index = { type: 'default' }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
await propSanitizer.call(this, { prop, schema: item, connection: conn, driver })
|
|
118
|
-
if (prop.index && prop.index.type === 'primary' && prop.name !== 'id') fatal.call(this, 'Primary index should only be used for \'id\' field')
|
|
119
|
-
if (prop.index && prop.index.type === 'fulltext') {
|
|
120
|
-
item.fullText.fields.push(prop.name)
|
|
121
|
-
prop.index.type = 'default'
|
|
122
|
-
}
|
|
123
|
-
item.properties[idx] = prop
|
|
124
|
-
}
|
|
125
|
-
await sanitizeIndexes.call(this, item)
|
|
126
|
-
await sanitizeFullText.call(this, item)
|
|
127
|
-
const all = []
|
|
128
|
-
each(item.properties, p => {
|
|
129
|
-
if (!all.includes(p.name)) all.push(p.name)
|
|
130
|
-
else fatal.call(this, 'Field \'%s@%s\' should be used only once', p.name, item.name)
|
|
131
|
-
})
|
|
132
|
-
file = `${ns}:/extend/${this.ns}/lib/${type}/schema-sanitizer.js`
|
|
133
|
-
const schemaSanitizer = await importModule(file)
|
|
134
|
-
if (schemaSanitizer) await schemaSanitizer.call(this, { schema: item, connection: conn, driver })
|
|
135
|
-
schemas.push(item)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
for (const i in schemas) {
|
|
139
|
-
const schema = schemas[i]
|
|
140
|
-
const sortables = []
|
|
141
|
-
const hidden = []
|
|
142
|
-
for (const i2 in schema.properties) {
|
|
143
|
-
const prop = schema.properties[i2]
|
|
144
|
-
if (prop.type !== 'string') delete prop.maxLength
|
|
145
|
-
if (prop.rel) {
|
|
146
|
-
for (const key in prop.rel) {
|
|
147
|
-
let def = prop.rel[key]
|
|
148
|
-
if (isString(def)) {
|
|
149
|
-
const [rschema, rfield] = def.split(':')
|
|
150
|
-
def = { schema: rschema, propName: rfield }
|
|
151
|
-
}
|
|
152
|
-
def.type = def.type ?? 'one-to-one'
|
|
153
|
-
const rel = find(schemas, { name: def.schema })
|
|
154
|
-
if (!rel) {
|
|
155
|
-
fatal.call(this, 'No schema found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
|
|
156
|
-
}
|
|
157
|
-
const rprop = find(rel.properties, { name: def.propName })
|
|
158
|
-
if (!rprop) fatal.call(this, 'No property found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
|
|
159
|
-
def.fields = def.fields ?? '*'
|
|
160
|
-
if (['*', 'all'].includes(def.fields)) {
|
|
161
|
-
const relschema = find(schemas, { name: def.schema })
|
|
162
|
-
def.fields = relschema.properties.map(p => p.name)
|
|
163
|
-
}
|
|
164
|
-
if (def.fields.length > 0 && !def.fields.includes('id')) def.fields.push('id')
|
|
165
|
-
for (const f of def.fields) {
|
|
166
|
-
const p = find(rel.properties, { name: f })
|
|
167
|
-
if (!p) fatal.call(this, 'Unknown property for field \'%s\' in relationship \'%s@%s:%s\'', p, `${def.schema}:${def.propName}`, schema.name, prop.name)
|
|
168
|
-
}
|
|
169
|
-
prop.type = rprop.type
|
|
170
|
-
if (rprop.type === 'string') prop.maxLength = rprop.maxLength
|
|
171
|
-
else {
|
|
172
|
-
delete prop.maxLength
|
|
173
|
-
delete prop.minLength
|
|
174
|
-
}
|
|
175
|
-
prop.rel[key] = def
|
|
176
|
-
}
|
|
177
|
-
// TODO: propSanitizer must be called again
|
|
178
|
-
}
|
|
179
|
-
if (prop.hidden) hidden.push(prop.name)
|
|
180
|
-
if (prop.index) sortables.push(prop.name)
|
|
181
|
-
delete prop.hidden
|
|
182
|
-
schema.properties[i2] = prop
|
|
183
|
-
}
|
|
184
|
-
// sortables
|
|
185
|
-
each(schema.indexes, item => {
|
|
186
|
-
sortables.push(...item.fields)
|
|
187
|
-
})
|
|
188
|
-
// hidden
|
|
189
|
-
schema.hidden = uniq(filter((schema.hidden ?? []).concat(hidden), item => map(schema.properties, 'name').includes(item)))
|
|
190
|
-
schema.sortables = sortables
|
|
191
|
-
await runHook(`dobo.${camelCase(schema.name)}:afterSanitizeSchema`, schema)
|
|
192
|
-
}
|
|
193
|
-
this.schemas = orderBy(schemas, ['buildLevel', 'name'])
|
|
194
|
-
freeze(this.schemas)
|
|
195
|
-
this.log.debug('loadedSchemas%s', join(map(this.schemas, 'name')))
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export default sanitizeSchema
|
package/lib/single-rel-rows.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
async function singleRelRows ({ schema, record, options = {} }) {
|
|
2
|
-
const { isSet } = this.app.lib.aneka
|
|
3
|
-
const { find } = this.app.lib._
|
|
4
|
-
const props = schema.properties.filter(p => isSet(p.rel) && !(options.hidden ?? []).includes(p.name))
|
|
5
|
-
const rels = {}
|
|
6
|
-
options.rels = options.rels ?? []
|
|
7
|
-
if (props.length > 0) {
|
|
8
|
-
for (const prop of props) {
|
|
9
|
-
for (const key in prop.rel) {
|
|
10
|
-
if (!((typeof options.rels === 'string' && ['*', 'all'].includes(options.rels)) || options.rels.includes(key))) continue
|
|
11
|
-
const val = prop.rel[key]
|
|
12
|
-
if (val.fields.length === 0) continue
|
|
13
|
-
const relschema = this.getSchema(val.schema)
|
|
14
|
-
const relFilter = options.filter ?? {}
|
|
15
|
-
const query = {}
|
|
16
|
-
query[val.propName] = record[prop.name]
|
|
17
|
-
if (val.propName === 'id') {
|
|
18
|
-
const idField = find(relschema.properties, { name: 'id' })
|
|
19
|
-
if (idField.type === 'integer') query[val.propName] = parseInt(query[val.propName])
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
relFilter.query = query
|
|
23
|
-
const relOptions = { dataOnly: true, rels: [] }
|
|
24
|
-
const results = await this.recordFind(relschema.name, relFilter, relOptions)
|
|
25
|
-
const fields = [...val.fields]
|
|
26
|
-
if (!fields.includes(prop.name)) fields.push(prop.name)
|
|
27
|
-
const data = []
|
|
28
|
-
for (const res of results) {
|
|
29
|
-
data.push(await this.pickRecord({ record: res, fields, schema: relschema }))
|
|
30
|
-
}
|
|
31
|
-
rels[key] = ['one-to-one'].includes(val.type) ? data[0] : data
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
record._rel = rels
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default singleRelRows
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
|
|
3
|
-
async function copyUploaded (name, id, options = {}) {
|
|
4
|
-
const { fs } = this.app.lib
|
|
5
|
-
const { req, setField, setFile, mimeType, stats, silent = true } = options
|
|
6
|
-
name = this.attachmentPreCheck(name)
|
|
7
|
-
if (!name) {
|
|
8
|
-
if (silent) return
|
|
9
|
-
throw this.error('isMissing%s', this.t('field.name'))
|
|
10
|
-
}
|
|
11
|
-
if (!this.app.waibu) {
|
|
12
|
-
if (silent) return
|
|
13
|
-
throw this.error('missingPlugin%s', 'Waibu')
|
|
14
|
-
}
|
|
15
|
-
const { dir, files } = await this.app.waibu.getUploadedFiles(req.id, false, true)
|
|
16
|
-
const result = []
|
|
17
|
-
if (files.length === 0) return result
|
|
18
|
-
for (const f of files) {
|
|
19
|
-
let [field, ...parts] = path.basename(f).split('@')
|
|
20
|
-
if (parts.length === 0) continue
|
|
21
|
-
field = setField ?? field
|
|
22
|
-
const file = setFile ?? parts.join('@')
|
|
23
|
-
const opts = { source: f, field, file, mimeType, stats, req, silent: true }
|
|
24
|
-
const rec = await this.attachmentCreate(name, id, opts)
|
|
25
|
-
if (!rec) continue
|
|
26
|
-
delete rec.dir
|
|
27
|
-
result.push(rec)
|
|
28
|
-
if (setField || setFile) break
|
|
29
|
-
}
|
|
30
|
-
fs.removeSync(dir)
|
|
31
|
-
return result
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default copyUploaded
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import mergeAttachmentInfo from '../../lib/merge-attachment-info.js'
|
|
2
|
-
|
|
3
|
-
async function create (name, id, options = {}) {
|
|
4
|
-
const { fs } = this.app.lib
|
|
5
|
-
const { isEmpty } = this.app.lib._
|
|
6
|
-
name = this.attachmentPreCheck(name)
|
|
7
|
-
if (!name) return
|
|
8
|
-
const { source, field = 'file', file } = options
|
|
9
|
-
if (isEmpty(file)) return
|
|
10
|
-
if (!source) throw this.error('isMissing%s', this.t('field.source'))
|
|
11
|
-
const baseDir = await this.attachmentGetPath(name, id, field, file, { dirOnly: true })
|
|
12
|
-
const { fullPath, stats, mimeType, req } = options
|
|
13
|
-
|
|
14
|
-
let dir = `${baseDir}/${field}`
|
|
15
|
-
if ((field || '').endsWith('[]')) dir = `${baseDir}/${field.replace('[]', '')}`
|
|
16
|
-
const dest = `${dir}/${file}`.replaceAll('//', '/')
|
|
17
|
-
await fs.ensureDir(dir)
|
|
18
|
-
await fs.copy(source, dest)
|
|
19
|
-
const rec = {
|
|
20
|
-
field: field === '' ? undefined : field,
|
|
21
|
-
dir,
|
|
22
|
-
file
|
|
23
|
-
}
|
|
24
|
-
await mergeAttachmentInfo.call(this, rec, dest, { mimeType, fullPath, stats })
|
|
25
|
-
if (!options.silent && req && req.flash) req.flash('notify', req.t('attachmentUploaded'))
|
|
26
|
-
return rec
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default create
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import mergeAttachmentInfo from '../../lib/merge-attachment-info.js'
|
|
2
|
-
|
|
3
|
-
async function find (name, id, options = {}) {
|
|
4
|
-
const { fastGlob, fs } = this.app.lib
|
|
5
|
-
const { getPluginDataDir } = this.app.bajo
|
|
6
|
-
name = this.attachmentPreCheck(name)
|
|
7
|
-
if (!name) return
|
|
8
|
-
const dir = `${getPluginDataDir(this.ns)}/attachment/${name}/${id}`
|
|
9
|
-
if (!fs.existsSync(dir)) return []
|
|
10
|
-
const files = await fastGlob(`${dir}/**/*`)
|
|
11
|
-
const { fullPath, stats, mimeType } = options
|
|
12
|
-
const recs = []
|
|
13
|
-
for (const f of files) {
|
|
14
|
-
const item = f.replace(dir, '')
|
|
15
|
-
let [, field, file] = item.split('/')
|
|
16
|
-
if (!file) {
|
|
17
|
-
file = field
|
|
18
|
-
field = null
|
|
19
|
-
}
|
|
20
|
-
const rec = { field, file }
|
|
21
|
-
await mergeAttachmentInfo.call(this, rec, f, { mimeType, fullPath, stats })
|
|
22
|
-
recs.push(rec)
|
|
23
|
-
}
|
|
24
|
-
return recs
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default find
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
async function getPath (name, id, field, file, options = {}) {
|
|
2
|
-
const { getPluginDataDir } = this.app.bajo
|
|
3
|
-
const { pascalCase } = this.app.lib.aneka
|
|
4
|
-
const { fs } = this.app.lib
|
|
5
|
-
const dir = `${getPluginDataDir(this.ns)}/attachment/${pascalCase(name)}/${id}`
|
|
6
|
-
if (options.dirOnly) return dir
|
|
7
|
-
const path = field ? `${dir}/${field}/${file}` : `${dir}/${file}`
|
|
8
|
-
if (!fs.existsSync(path)) throw this.error('notFound')
|
|
9
|
-
return path
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default getPath
|