dobo 2.0.0 → 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 +13 -0
- package/.github/workflows/repo-lockdown.yml +24 -0
- package/.jsdoc.conf.json +45 -0
- package/LICENSE +1 -1
- package/README.md +38 -19
- package/docs/Dobo.html +26 -0
- package/docs/data/search.json +1 -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 +7 -0
- package/docs/index.html +3 -0
- package/docs/index.js.html +578 -0
- package/docs/lib_collect-connections.js.html +39 -0
- package/docs/lib_collect-drivers.js.html +52 -0
- package/docs/lib_collect-features.js.html +36 -0
- package/docs/lib_collect-schemas.js.html +94 -0
- package/docs/lib_index.js.html +6 -0
- package/docs/method_model_create.js.html +35 -0
- package/docs/method_model_drop.js.html +34 -0
- package/docs/method_model_exists.js.html +40 -0
- package/docs/method_record_count.js.html +69 -0
- package/docs/method_record_create.js.html +114 -0
- package/docs/method_record_find-all.js.html +44 -0
- package/docs/method_record_find-one.js.html +73 -0
- package/docs/method_record_find.js.html +118 -0
- package/docs/method_record_get.js.html +92 -0
- package/docs/method_record_remove.js.html +75 -0
- package/docs/method_record_update.js.html +107 -0
- package/docs/method_record_upsert.js.html +54 -0
- package/docs/method_sanitize_body.js.html +88 -0
- package/docs/method_sanitize_date.js.html +30 -0
- package/docs/method_sanitize_id.js.html +20 -0
- package/docs/method_validate.js.html +249 -0
- package/docs/module-Lib.html +3 -0
- package/docs/scripts/core.js +725 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +366 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5164 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +671 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/static/bitcoin.jpeg +0 -0
- package/docs/static/home.md +25 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/extend/bajo/intl/en-US.json +69 -30
- package/extend/bajo/intl/id.json +58 -29
- package/extend/bajoCli/applet/clear-record.js +22 -0
- package/extend/bajoCli/applet/connection.js +5 -5
- 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 +25 -26
- 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 +10 -8
- 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 +35 -57
- package/extend/dobo/feature/updated-at.js +14 -12
- package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +5 -9
- package/extend/waibuStatic/virtual.json +0 -0
- package/index.js +420 -337
- package/lib/collect-connections.js +60 -21
- package/lib/collect-drivers.js +29 -35
- package/lib/collect-features.js +40 -0
- 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/lib/factory/model/find-record.js +103 -0
- 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/lib/factory/model/validate.js +232 -0
- package/lib/factory/model.js +150 -0
- package/lib/index.js +3 -0
- package/package.json +45 -36
- package/wiki/APPLETS.md +57 -0
- package/wiki/CHANGES.md +46 -0
- package/wiki/CONFIG.md +25 -0
- package/wiki/CONTRIBUTING.md +5 -0
- package/wiki/DEV-GUIDE.md +1 -0
- package/wiki/ECOSYSTEM.md +20 -0
- package/wiki/GETTING-STARTED.md +166 -0
- package/{docs/query-language.md → wiki/QUERY-LANGUAGE.md} +0 -0
- package/wiki/USER-GUIDE.md +1 -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 -41
- package/extend/bajoCli/applet/record-find.js +0 -27
- 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-feature.js +0 -25
- package/lib/collect-schemas.js +0 -83
- package/lib/exec-feature-hook.js +0 -13
- package/lib/exec-validation.js +0 -21
- package/lib/generic-prop-sanitizer.js +0 -31
- 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 -197
- 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 -19
- package/method/model/drop.js +0 -19
- package/method/model/exists.js +0 -24
- package/method/record/clear.js +0 -24
- package/method/record/count.js +0 -44
- package/method/record/create.js +0 -71
- package/method/record/find-all.js +0 -25
- package/method/record/find-one.js +0 -56
- package/method/record/find.js +0 -52
- package/method/record/get.js +0 -47
- package/method/record/remove.js +0 -41
- package/method/record/update.js +0 -63
- package/method/record/upsert.js +0 -35
- package/method/sanitize/body.js +0 -70
- package/method/sanitize/date.js +0 -14
- package/method/sanitize/id.js +0 -7
- package/method/stat/aggregate.js +0 -23
- package/method/stat/histogram.js +0 -26
- package/method/validate.js +0 -157
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execValidation, execModelHook, getSingleRef, handleReq } from './_util.js'
|
|
2
|
+
import { onlyTypes } from './create-record.js'
|
|
3
|
+
const action = 'updateRecord'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} TRecordUpdateOptions
|
|
7
|
+
* @see Model#updateRecord
|
|
8
|
+
* @property {boolean} [dataOnly=true] - If ```true``` (default) returns record's object. Otherwise {@link TRecordUpdateResult}
|
|
9
|
+
* @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
|
|
10
|
+
* @property {boolean} [noModelHook=false] - If ```true```, no model's hook will be executed
|
|
11
|
+
* @property {boolean} [noValidation=false] - If ```true```, no validation of data payload performed
|
|
12
|
+
* @property {boolean} [noCheckUnique=false] - If ```true```, no unique validation for ID performed
|
|
13
|
+
* @property {boolean} [noBodySanitizer=false] - If ```true```, accept data payload as is without sanitization
|
|
14
|
+
* @property {boolean} [noRecordSanitizer=false] - If ```true```, accept result payload as is without sanitization
|
|
15
|
+
* @property {boolean} [noResult=false] - If ```true```, returns nothing
|
|
16
|
+
* @property {boolean} [truncateString=true] - If ```true``` (default), string is truncated to its model's ```maxLength```
|
|
17
|
+
* @property {boolean} [partial=true] - If ```true``` (default), only updated values are saved. Otherwise replace all existing values with given payload
|
|
18
|
+
* @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
|
|
19
|
+
* @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
|
|
20
|
+
* @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Update a record by it's ID and body payload
|
|
25
|
+
*
|
|
26
|
+
* Example:
|
|
27
|
+
* ```javascript
|
|
28
|
+
* const { recordUpdate } = this.app.dobo
|
|
29
|
+
* const { body } = {
|
|
30
|
+
* name: 'Republic of Indonesia',
|
|
31
|
+
* phoneCode: '+62'
|
|
32
|
+
* }
|
|
33
|
+
* const result = await recordUpdate('CdbCountry', 'ID', body)
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @method
|
|
37
|
+
* @memberof Dobo
|
|
38
|
+
* @async
|
|
39
|
+
* @instance
|
|
40
|
+
* @name recordUpdate
|
|
41
|
+
* @param {string} name - Model's name
|
|
42
|
+
* @param {(string|number)} id - Record's ID
|
|
43
|
+
* @param {Object} body - Body payload
|
|
44
|
+
* @param {TRecordUpdateOptions} [options={}]
|
|
45
|
+
* @returns {(TRecordUpdateResult|Object)} Returns updated record if ```options.dataOnly``` is set. {@link TRecordUpdateResult} otherwise
|
|
46
|
+
*/
|
|
47
|
+
async function updateRecord (...args) {
|
|
48
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
49
|
+
let [id, body = {}, opts = {}] = args
|
|
50
|
+
const { isSet } = this.app.lib.aneka
|
|
51
|
+
const { runHook } = this.app.bajo
|
|
52
|
+
const { cloneDeep, get } = this.app.lib._
|
|
53
|
+
const { dataOnly = true } = opts
|
|
54
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
55
|
+
options.partial = options.partial ?? true
|
|
56
|
+
const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation, partial } = options
|
|
57
|
+
const extFields = get(options, 'validation.extFields', [])
|
|
58
|
+
id = this.sanitizeId(id)
|
|
59
|
+
const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, partial, onlyTypes })
|
|
60
|
+
delete input.id
|
|
61
|
+
await execHook.call(this, 'beforeUpdateRecord', id, input, options)
|
|
62
|
+
await execModelHook.call(this, 'beforeUpdateRecord', id, input, options)
|
|
63
|
+
if (!noValidation) await execValidation.call(this, input, options)
|
|
64
|
+
const result = options.record ?? (await this.driver._updateRecord(this, id, input, options)) ?? {}
|
|
65
|
+
if (noResult) {
|
|
66
|
+
await runHook('cache:clear', this, 'update', id, body)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
if (!noResultSanitizer) {
|
|
70
|
+
result.data = await this.sanitizeRecord(result.data, options)
|
|
71
|
+
result.oldData = await this.sanitizeRecord(result.oldData, options)
|
|
72
|
+
}
|
|
73
|
+
if (isSet(options.refs)) await getSingleRef.call(this, { record: result.data, options })
|
|
74
|
+
await handleReq.call(this, result.data.id, 'updated', options)
|
|
75
|
+
await execModelHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
76
|
+
await execHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
77
|
+
await runHook('cache:clear', this, 'update', id, body, result)
|
|
78
|
+
return dataOnly ? result.data : result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default updateRecord
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook, execValidation, getSingleRef, handleReq } from './_util.js'
|
|
2
|
+
const action = 'upsertRecord'
|
|
3
|
+
|
|
4
|
+
async function native (body = {}, opts = {}) {
|
|
5
|
+
const { isSet } = this.app.lib.aneka
|
|
6
|
+
const { runHook } = this.app.bajo
|
|
7
|
+
const { cloneDeep, get } = this.app.lib._
|
|
8
|
+
const { dataOnly = true } = opts
|
|
9
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
10
|
+
const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
|
|
11
|
+
const extFields = get(options, 'validation.extFields', [])
|
|
12
|
+
let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString })
|
|
13
|
+
if (!noValidation) input = await execValidation.call(this, input, options)
|
|
14
|
+
await execHook.call(this, 'beforeUpsertRecord', input, options)
|
|
15
|
+
await execModelHook.call(this, 'beforeUpsertRecord', input, options)
|
|
16
|
+
const result = options.record ?? (await this.driver._upsertRecord(this, input, options)) ?? {}
|
|
17
|
+
if (noResult) {
|
|
18
|
+
await runHook('cache:clear', this, 'upsert', body)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
22
|
+
if (isSet(options.refs)) await getSingleRef.call(this, { record: result.data, options })
|
|
23
|
+
await handleReq.call(this, result.data.id, 'upserted', options)
|
|
24
|
+
await execModelHook.call(this, 'afterUpsertRecord', input, result, options)
|
|
25
|
+
await execHook.call(this, 'afterUpsertRecord', input, result, options)
|
|
26
|
+
await runHook('cache:clear', this, 'upsert', body, result)
|
|
27
|
+
return dataOnly ? result.data : result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function manual (body = {}, options = {}) {
|
|
31
|
+
const { isSet } = this.app.lib.aneka
|
|
32
|
+
if (isSet(body.id)) body.id = this.sanitizeId(body.id)
|
|
33
|
+
let old = false
|
|
34
|
+
if (isSet(body.id)) {
|
|
35
|
+
try {
|
|
36
|
+
old = await this.driver._getRecord(this, body.id, { noHook: true, noModelHook: true })
|
|
37
|
+
} catch (err) {}
|
|
38
|
+
}
|
|
39
|
+
if (old) return await this.updateRecord(old.data.id, body, options)
|
|
40
|
+
return await this.createRecord(body, options)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {Object} TRecordUpdateOptions
|
|
45
|
+
* @see Model#updateRecord
|
|
46
|
+
* @property {boolean} [dataOnly=true] - If ```true``` (default) returns record's object. Otherwise {@link TRecordUpdateResult}
|
|
47
|
+
* @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
|
|
48
|
+
* @property {boolean} [noModelHook=false] - If ```true```, no model's hook will be executed
|
|
49
|
+
* @property {boolean} [noValidation=false] - If ```true```, no validation of data payload performed
|
|
50
|
+
* @property {boolean} [noCheckUnique=false] - If ```true```, no unique validation for ID performed
|
|
51
|
+
* @property {boolean} [noBodySanitizer=false] - If ```true```, accept data payload as is without sanitization
|
|
52
|
+
* @property {boolean} [noRecordSanitizer=false] - If ```true```, accept result payload as is without sanitization
|
|
53
|
+
* @property {boolean} [noResult=false] - If ```true```, returns nothing
|
|
54
|
+
* @property {boolean} [truncateString=true] - If ```true``` (default), string is truncated to its model's ```maxLength```
|
|
55
|
+
* @property {boolean} [partial=true] - If ```true``` (default), only updated values are saved. Otherwise replace all existing values with given payload
|
|
56
|
+
* @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
|
|
57
|
+
* @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
|
|
58
|
+
* @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Update a record by it's ID and body payload
|
|
63
|
+
*
|
|
64
|
+
* Example:
|
|
65
|
+
* ```javascript
|
|
66
|
+
* const { recordUpdate } = this.app.dobo
|
|
67
|
+
* const { body } = {
|
|
68
|
+
* name: 'Republic of Indonesia',
|
|
69
|
+
* phoneCode: '+62'
|
|
70
|
+
* }
|
|
71
|
+
* const result = await recordUpdate('CdbCountry', 'ID', body)
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @method
|
|
75
|
+
* @memberof Dobo
|
|
76
|
+
* @async
|
|
77
|
+
* @instance
|
|
78
|
+
* @name recordUpdate
|
|
79
|
+
* @param {string} name - Model's name
|
|
80
|
+
* @param {(string|number)} id - Record's ID
|
|
81
|
+
* @param {Object} body - Body payload
|
|
82
|
+
* @param {TRecordUpdateOptions} [options={}]
|
|
83
|
+
* @returns {(TRecordUpdateResult|Object)} Returns updated record if ```options.dataOnly``` is set. {@link TRecordUpdateResult} otherwise
|
|
84
|
+
*/
|
|
85
|
+
async function upsertRecord (...args) {
|
|
86
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
87
|
+
const [body = {}, opts = {}] = args
|
|
88
|
+
if (this.driver.upsertRecord) {
|
|
89
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
90
|
+
return await native.call(this, body, options)
|
|
91
|
+
}
|
|
92
|
+
return await manual.call(this, body, opts)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default upsertRecord
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import joi from 'joi'
|
|
2
|
+
|
|
3
|
+
const excludedTypes = ['object', 'array']
|
|
4
|
+
const excludedNames = []
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {string[]} TValidatorString
|
|
8
|
+
* @property {string} 0=alphanum
|
|
9
|
+
* @property {string} 1=base64
|
|
10
|
+
* @property {string} 2=case
|
|
11
|
+
* @property {string} 3=creditCard
|
|
12
|
+
* @property {string} 4=dataUri
|
|
13
|
+
* @property {string} 5=email
|
|
14
|
+
* @property {string} 6=guid
|
|
15
|
+
* @property {string} 7=uuid
|
|
16
|
+
* @property {string} 8=hex
|
|
17
|
+
* @property {string} 9=hostname
|
|
18
|
+
* @property {string} 10=insenstive
|
|
19
|
+
* @property {string} 11=ip
|
|
20
|
+
* @property {string} 12=isoDate
|
|
21
|
+
* @property {string} 13=isoDuration
|
|
22
|
+
* @property {string} 14=length
|
|
23
|
+
* @property {string} 15=lowercase
|
|
24
|
+
* @property {string} 16=max
|
|
25
|
+
* @property {string} 17=min
|
|
26
|
+
* @property {string} 18=normalize
|
|
27
|
+
* @property {string} 19=pattern
|
|
28
|
+
* @property {string} 20=regex
|
|
29
|
+
* @property {string} 21=replace
|
|
30
|
+
* @property {string} 22=token
|
|
31
|
+
* @property {string} 23=trim
|
|
32
|
+
* @property {string} 24=truncate
|
|
33
|
+
* @property {string} 25=upercase
|
|
34
|
+
* @property {string} 26=uri
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {string[]} TValidatorNumber
|
|
39
|
+
* @property {string} 0=great
|
|
40
|
+
* @property {string} 1=less
|
|
41
|
+
* @property {string} 2=max
|
|
42
|
+
* @property {string} 3=min
|
|
43
|
+
* @property {string} 4=multiple
|
|
44
|
+
* @property {string} 5=negative
|
|
45
|
+
* @property {string} 6=port
|
|
46
|
+
* @property {string} 7=positive
|
|
47
|
+
* @property {string} 8=sign
|
|
48
|
+
* @property {string} 9=unsafe
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {string[]} TValidatorBoolean
|
|
53
|
+
* @property {string} 0=falsy
|
|
54
|
+
* @property {string} 1=sensitive
|
|
55
|
+
* @property {string} 2=truthy
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {string[]} TValidatorDate
|
|
60
|
+
* @property {string} 0=greater
|
|
61
|
+
* @property {string} 1=iso
|
|
62
|
+
* @property {string} 2=less
|
|
63
|
+
* @property {string} 2=max
|
|
64
|
+
* @property {string} 2=min
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {string[]} TValidatorTimestamp
|
|
69
|
+
* @property {string} 0=timestamp
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {Object} TValidator
|
|
74
|
+
* @property {TValidatorString} string
|
|
75
|
+
* @property {TValidatorNumber} number
|
|
76
|
+
* @property {TValidatorBoolean} boolean
|
|
77
|
+
* @property {TValidatorDate} date
|
|
78
|
+
* @property {TValidatorTimestamp} timestamp
|
|
79
|
+
*/
|
|
80
|
+
const validator = {
|
|
81
|
+
string: ['alphanum', 'base64', 'case', 'creditCard', 'dataUri', 'domain', 'email', 'guid',
|
|
82
|
+
'uuid', 'hex', 'hostname', 'insensitive', 'ip', 'isoDate', 'isoDuration', 'length', 'lowercase',
|
|
83
|
+
'max', 'min', 'normalize', 'pattern', 'regex', 'replace', 'token', 'trim', 'truncate',
|
|
84
|
+
'uppercase', 'uri'],
|
|
85
|
+
number: ['great', 'less', 'max', 'min', 'multiple', 'negative', 'port', 'positive',
|
|
86
|
+
'sign', 'unsafe'],
|
|
87
|
+
boolean: ['falsy', 'sensitive', 'truthy'],
|
|
88
|
+
date: ['greater', 'iso', 'less', 'max', 'min'],
|
|
89
|
+
timestamp: ['timestamp']
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildFromDbModel (opts = {}) {
|
|
93
|
+
const { isPlainObject, get, isEmpty, isString, keys, find, has, without } = this.app.lib._
|
|
94
|
+
const { fields = [], rule = {}, extFields = [] } = opts
|
|
95
|
+
const obj = {}
|
|
96
|
+
const { propertyType: propType } = this.app.baseClass.Dobo
|
|
97
|
+
const refs = []
|
|
98
|
+
|
|
99
|
+
function getRuleKv (kvRule) {
|
|
100
|
+
let key
|
|
101
|
+
let value
|
|
102
|
+
let columns
|
|
103
|
+
if (isPlainObject(kvRule)) {
|
|
104
|
+
key = kvRule.rule
|
|
105
|
+
value = kvRule.params
|
|
106
|
+
columns = kvRule.fields
|
|
107
|
+
} else if (isString(kvRule)) {
|
|
108
|
+
[key, value, columns] = kvRule.split(':')
|
|
109
|
+
}
|
|
110
|
+
return { key, value, columns }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function applyFieldRules (prop, obj) {
|
|
114
|
+
const minMax = { min: false, max: false }
|
|
115
|
+
const rules = get(rule, prop.name, prop.rules ?? [])
|
|
116
|
+
if (!Array.isArray(rules)) return rules
|
|
117
|
+
for (const r of rules) {
|
|
118
|
+
const types = validator[propType[prop.type].validator]
|
|
119
|
+
const { key, value } = getRuleKv(r)
|
|
120
|
+
if (keys(minMax).includes(key)) minMax[key] = true
|
|
121
|
+
if (key === 'ref') {
|
|
122
|
+
refs.push(prop.name)
|
|
123
|
+
obj = joi.ref(value)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
if (!key || !types.includes(key)) continue
|
|
127
|
+
obj = obj[key](value)
|
|
128
|
+
}
|
|
129
|
+
if (refs.includes(prop.name)) return obj
|
|
130
|
+
if (['string', 'text'].includes(prop.type)) {
|
|
131
|
+
for (const k in minMax) {
|
|
132
|
+
if (minMax[k]) continue
|
|
133
|
+
if (has(prop, `${k}Length`)) obj = obj[k](prop[`${k}Length`])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(prop.values)) obj = obj.valid(...prop.values)
|
|
137
|
+
if (!['id'].includes(prop.name) && prop.required) obj = obj.required()
|
|
138
|
+
return obj
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const props = [...this.properties, ...extFields]
|
|
142
|
+
|
|
143
|
+
for (const p of props) {
|
|
144
|
+
if (excludedTypes.includes(p.type) || excludedNames.includes(p.name)) continue
|
|
145
|
+
if (opts.partial && fields.length > 0 && !fields.includes(p.name)) continue
|
|
146
|
+
let item
|
|
147
|
+
switch (p.type) {
|
|
148
|
+
case 'text':
|
|
149
|
+
case 'string': {
|
|
150
|
+
item = applyFieldRules(p, joi.string())
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
case 'smallint':
|
|
154
|
+
case 'integer':
|
|
155
|
+
item = applyFieldRules(p, joi.number().integer())
|
|
156
|
+
break
|
|
157
|
+
case 'float':
|
|
158
|
+
case 'double':
|
|
159
|
+
if (p.precision) item = applyFieldRules(p, joi.number().precision(p.precision))
|
|
160
|
+
else item = applyFieldRules(p, joi.number())
|
|
161
|
+
break
|
|
162
|
+
case 'time':
|
|
163
|
+
case 'date':
|
|
164
|
+
case 'datetime':
|
|
165
|
+
item = applyFieldRules(p, joi.date())
|
|
166
|
+
break
|
|
167
|
+
case 'timestamp':
|
|
168
|
+
item = applyFieldRules(p, joi.number().integer())
|
|
169
|
+
break
|
|
170
|
+
case 'boolean':
|
|
171
|
+
item = applyFieldRules(p, joi.boolean())
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
if (item) {
|
|
175
|
+
if (item.$_root && !p.required) obj[p.name] = item.allow(null, '')
|
|
176
|
+
else obj[p.name] = item
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (isEmpty(obj)) return false
|
|
180
|
+
for (const r of this.rules) {
|
|
181
|
+
for (const k of without(keys(obj), ...refs)) {
|
|
182
|
+
const prop = find(props, { name: k })
|
|
183
|
+
if (!prop) continue
|
|
184
|
+
const types = validator[propType[prop.type].validator]
|
|
185
|
+
const { key, value, columns = [] } = getRuleKv(r)
|
|
186
|
+
if (!types.includes(key)) continue
|
|
187
|
+
if (columns.length === 0 || columns.includes(k)) obj[k] = obj[k][key](value)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const result = joi.object(obj)
|
|
191
|
+
for (const k of ['with', 'xor', 'without']) {
|
|
192
|
+
const item = get(this, `extRule.${k}`)
|
|
193
|
+
if (item) result[k](...item)
|
|
194
|
+
}
|
|
195
|
+
return result
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate value against JOI model
|
|
200
|
+
*
|
|
201
|
+
* @method
|
|
202
|
+
* @memberof Dobo
|
|
203
|
+
* @async
|
|
204
|
+
* @param {Object} value - value to validate
|
|
205
|
+
* @param {Object} joiModel - JOI model
|
|
206
|
+
* @param {Object} [options={}] - Options object
|
|
207
|
+
* @param {string} [options.ns=dobo] - Scope's namespace
|
|
208
|
+
* @param {Array} [options.fields=[]]
|
|
209
|
+
* @param {Array} [options.extFields=[]]
|
|
210
|
+
* @param {Object} [options.params={}] - Validation parameters. See {@tutorial config} and {@link https://joi.dev/api/?v=17.13.3#anyvalidateasyncvalue-options|JOI validate's options}
|
|
211
|
+
* @returns {Object}
|
|
212
|
+
*/
|
|
213
|
+
async function validate (body, joiModel, opts = {}) {
|
|
214
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
215
|
+
const { isEmpty } = this.app.lib._
|
|
216
|
+
let { fields, extFields = [], params = {}, partial } = opts
|
|
217
|
+
|
|
218
|
+
params = defaultsDeep(params, this.app.dobo.config.validationParams)
|
|
219
|
+
const { rule = {} } = params
|
|
220
|
+
delete params.rule
|
|
221
|
+
if (isEmpty(joiModel)) joiModel = buildFromDbModel.call(this, { fields, rule, extFields, partial })
|
|
222
|
+
if (!joiModel) return { value: body }
|
|
223
|
+
try {
|
|
224
|
+
return await joiModel.validateAsync(body, params)
|
|
225
|
+
} catch (err) {
|
|
226
|
+
const payload = { details: err.details, statusCode: 422, code: 'DB_VALIDATION' }
|
|
227
|
+
if (err.values) payload.values = err.values
|
|
228
|
+
throw this.plugin.error('validationError', payload)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default validate
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { sanitizeAll, sanitizeRef } from '../collect-models.js'
|
|
2
|
+
import clearRecord from './model/clear-record.js'
|
|
3
|
+
import countRecord from './model/count-record.js'
|
|
4
|
+
import createRecord from './model/create-record.js'
|
|
5
|
+
import drop from './model/drop.js'
|
|
6
|
+
import findAllRecord from './model/find-all-record.js'
|
|
7
|
+
import findOneRecord from './model/find-one-record.js'
|
|
8
|
+
import findRecord from './model/find-record.js'
|
|
9
|
+
import getRecord from './model/get-record.js'
|
|
10
|
+
import createHistogram from './model/create-histogram.js'
|
|
11
|
+
import createAggregate from './model/create-aggregate.js'
|
|
12
|
+
import exists from './model/exists.js'
|
|
13
|
+
import build from './model/build.js'
|
|
14
|
+
import loadFixtures from './model/load-fixtures.js'
|
|
15
|
+
import removeRecord from './model/remove-record.js'
|
|
16
|
+
import updateRecord from './model/update-record.js'
|
|
17
|
+
import createAttachment from './model/create-attachment.js'
|
|
18
|
+
import updateAttachment from './model/update-attachment.js'
|
|
19
|
+
import getAttachment from './model/get-attachment.js'
|
|
20
|
+
import removeAttachment from './model/remove-attachment.js'
|
|
21
|
+
import findAttachment from './model/find-attachment.js'
|
|
22
|
+
import sanitizeBody from './model/sanitize-body.js'
|
|
23
|
+
import sanitizeRecord from './model/sanitize-record.js'
|
|
24
|
+
import sanitizeId from './model/sanitize-id.js'
|
|
25
|
+
import upsertRecord from './model/upsert-record.js'
|
|
26
|
+
import listAttachment from './model/list-attachment.js'
|
|
27
|
+
import validate from './model/validate.js'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {string} TRecordSortKey
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Key value pairs used as sort information:
|
|
35
|
+
* - Key represent model's field name
|
|
36
|
+
* - value represent its sort order: ```1``` for ascending order, and ```-1``` for descending order
|
|
37
|
+
*
|
|
38
|
+
* Example: to sort by firstName (ascending) and lastName (descending)
|
|
39
|
+
* ```javascript
|
|
40
|
+
* const sort = {
|
|
41
|
+
* firstName: 1,
|
|
42
|
+
* lastName: -1
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @typedef {Object.<string, TRecordSortKey>} TRecordSort
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {Object} TRecordPagination
|
|
51
|
+
* @property {number} limit - Number of records per page
|
|
52
|
+
* @property {number} page - Page number
|
|
53
|
+
* @property {number} skip - Records to skip
|
|
54
|
+
* @property {TRecordSort} sort - Sort order
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
async function modelFactory () {
|
|
58
|
+
const { Tools } = this.app.baseClass
|
|
59
|
+
const { defaults } = this.app.lib._
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Feature class
|
|
63
|
+
*
|
|
64
|
+
* ```this.plugin``` should be the one who owned this driver
|
|
65
|
+
*
|
|
66
|
+
* @class
|
|
67
|
+
*/
|
|
68
|
+
class DoboModel extends Tools {
|
|
69
|
+
constructor (plugin, options) {
|
|
70
|
+
super(plugin)
|
|
71
|
+
defaults(this, options)
|
|
72
|
+
this.driver = this.connection.driver
|
|
73
|
+
this.cacheable = this.cacheable ?? this.driver.cacheable ?? true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
action = (name, ...args) => {
|
|
77
|
+
const action = new this.app.baseClass.DoboAction(this, name, ...args)
|
|
78
|
+
return action
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
sanitizeObjectDef = async (obj) => {
|
|
82
|
+
await sanitizeAll.call(this.app.dobo, obj)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
sanitizeRef = async (obj, fatal = true) => {
|
|
86
|
+
await sanitizeRef.call(this.app.dobo, obj, this.app.dobo.models, fatal)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getProperty = (name) => {
|
|
90
|
+
return this.properties.find(prop => prop.name === name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
hasProperty = (name) => {
|
|
94
|
+
return !!this.getProperty(name)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
build = build
|
|
98
|
+
exists = exists
|
|
99
|
+
drop = drop
|
|
100
|
+
|
|
101
|
+
createRecord = createRecord
|
|
102
|
+
getRecord = getRecord
|
|
103
|
+
updateRecord = updateRecord
|
|
104
|
+
upsertRecord = upsertRecord
|
|
105
|
+
removeRecord = removeRecord
|
|
106
|
+
clearRecord = clearRecord
|
|
107
|
+
findRecord = findRecord
|
|
108
|
+
findOneRecord = findOneRecord
|
|
109
|
+
findAllRecord = findAllRecord
|
|
110
|
+
|
|
111
|
+
createAggregate = createAggregate
|
|
112
|
+
createHistogram = createHistogram
|
|
113
|
+
countRecord = countRecord
|
|
114
|
+
|
|
115
|
+
createAttachment = createAttachment
|
|
116
|
+
getAttachment = getAttachment
|
|
117
|
+
updateAttachment = updateAttachment
|
|
118
|
+
removeAttachment = removeAttachment
|
|
119
|
+
findAttachment = findAttachment
|
|
120
|
+
listAttachment = listAttachment
|
|
121
|
+
|
|
122
|
+
loadFixtures = loadFixtures
|
|
123
|
+
sanitizeRecord = sanitizeRecord
|
|
124
|
+
sanitizeBody = sanitizeBody
|
|
125
|
+
sanitizeId = sanitizeId
|
|
126
|
+
validate = validate
|
|
127
|
+
|
|
128
|
+
// aliases
|
|
129
|
+
deleteRecord = removeRecord
|
|
130
|
+
clearRecords = clearRecord
|
|
131
|
+
countRecords = countRecord
|
|
132
|
+
findRecords = findRecord
|
|
133
|
+
findAllRecords = findAllRecord
|
|
134
|
+
listAttachments = listAttachment
|
|
135
|
+
|
|
136
|
+
getField = (name) => this.getProperty(name)
|
|
137
|
+
hasField = (name) => this.hasProperty(name)
|
|
138
|
+
|
|
139
|
+
dispose () {
|
|
140
|
+
super.dispose()
|
|
141
|
+
this.connection = null
|
|
142
|
+
this.driver = null
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.app.baseClass.DoboModel = DoboModel
|
|
147
|
+
return DoboModel
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default modelFactory
|
package/lib/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dobo",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dobo",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "DBMS for Bajo Framework",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build-doc": "jsdoc -c .jsdoc.conf.json",
|
|
8
|
+
"test": "mocha"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bajo": {
|
|
12
|
+
"type": "plugin",
|
|
13
|
+
"alias": "db",
|
|
14
|
+
"bootorder": 50
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/ardhi/dobo.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"database",
|
|
22
|
+
"dobo",
|
|
23
|
+
"db",
|
|
24
|
+
"orm",
|
|
25
|
+
"bajo",
|
|
26
|
+
"framework"
|
|
27
|
+
],
|
|
28
|
+
"author": "Ardhi Lukianto <ardhi@lukianto.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/ardhi/dobo/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/ardhi/dobo#readme",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@tryghost/nql": "^0.12.7",
|
|
36
|
+
"joi": "^18.0.2",
|
|
37
|
+
"mingo": "^7.1.0",
|
|
38
|
+
"ulid": "^3.0.2",
|
|
39
|
+
"uuid": "^13.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"clean-jsdoc-theme": "^4.3.0",
|
|
43
|
+
"jsdoc-plugin-intersection": "^1.0.4"
|
|
44
|
+
}
|
|
45
|
+
}
|