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
package/index.js
CHANGED
|
@@ -1,327 +1,349 @@
|
|
|
1
1
|
import collectConnections from './lib/collect-connections.js'
|
|
2
2
|
import collectDrivers from './lib/collect-drivers.js'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import memDbStart from './lib/mem-db/start.js'
|
|
6
|
-
import memDbInstantiate from './lib/mem-db/instantiate.js'
|
|
7
|
-
import nql from '@tryghost/nql'
|
|
8
|
-
import path from 'path'
|
|
3
|
+
import collectFeatures from './lib/collect-features.js'
|
|
4
|
+
import collectModels from './lib/collect-models.js'
|
|
9
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} TPropertyType
|
|
8
|
+
* @property {Object} integer
|
|
9
|
+
* @property {string} [integer.validator=number]
|
|
10
|
+
* @property {Object} smallint
|
|
11
|
+
* @property {string} [smallint.validator=number]
|
|
12
|
+
* @property {Object} text
|
|
13
|
+
* @property {string} [text.validator=string]
|
|
14
|
+
* @property {string} [text.textType=string]
|
|
15
|
+
* @property {string[]} [text.values=['text', 'mediumtext', 'longtext']]
|
|
16
|
+
* @property {Object} string
|
|
17
|
+
* @property {string} [string.validator=string]
|
|
18
|
+
* @property {maxLength} [string.maxLength=255]
|
|
19
|
+
* @property {minLength} [string.minLength=0]
|
|
20
|
+
* @property {Object} float
|
|
21
|
+
* @property {string} [float.validator=number]
|
|
22
|
+
* @property {Object} double
|
|
23
|
+
* @property {string} [double.validator=number]
|
|
24
|
+
* @property {Object} boolean
|
|
25
|
+
* @property {string} [boolean.validator=boolean]
|
|
26
|
+
* @property {Object} datetime
|
|
27
|
+
* @property {string} [datetime.validator=date]
|
|
28
|
+
* @property {Object} date
|
|
29
|
+
* @property {string} [date.validator=date]
|
|
30
|
+
* @property {Object} time
|
|
31
|
+
* @property {string} [time.validator=date]
|
|
32
|
+
* @property {Object} timestamp
|
|
33
|
+
* @property {string} [timestamp.validator=timestamp]
|
|
34
|
+
* @property {Object} object={}
|
|
35
|
+
* @property {Object} array={}
|
|
36
|
+
*/
|
|
37
|
+
const propertyType = {
|
|
38
|
+
integer: {
|
|
39
|
+
validator: 'number',
|
|
40
|
+
rules: []
|
|
41
|
+
},
|
|
42
|
+
smallint: {
|
|
43
|
+
validator: 'number',
|
|
44
|
+
rules: []
|
|
45
|
+
},
|
|
46
|
+
text: {
|
|
47
|
+
validator: 'string',
|
|
48
|
+
textType: 'text',
|
|
49
|
+
values: ['text', 'mediumtext', 'longtext'],
|
|
50
|
+
rules: []
|
|
51
|
+
},
|
|
52
|
+
string: {
|
|
53
|
+
validator: 'string',
|
|
54
|
+
maxLength: 50,
|
|
55
|
+
minLength: 0,
|
|
56
|
+
rules: []
|
|
57
|
+
},
|
|
58
|
+
float: {
|
|
59
|
+
validator: 'number',
|
|
60
|
+
rules: []
|
|
61
|
+
},
|
|
62
|
+
double: {
|
|
63
|
+
validator: 'number',
|
|
64
|
+
rules: []
|
|
65
|
+
},
|
|
66
|
+
boolean: {
|
|
67
|
+
validator: 'boolean',
|
|
68
|
+
rules: []
|
|
69
|
+
},
|
|
70
|
+
date: {
|
|
71
|
+
validator: 'date',
|
|
72
|
+
rules: []
|
|
73
|
+
},
|
|
74
|
+
datetime: {
|
|
75
|
+
validator: 'date',
|
|
76
|
+
rules: []
|
|
77
|
+
},
|
|
78
|
+
time: {
|
|
79
|
+
validator: 'date',
|
|
80
|
+
rules: []
|
|
81
|
+
},
|
|
82
|
+
timestamp: {
|
|
83
|
+
validator: 'timestamp',
|
|
84
|
+
rules: []
|
|
85
|
+
},
|
|
86
|
+
object: {
|
|
87
|
+
validator: null,
|
|
88
|
+
rules: []
|
|
89
|
+
},
|
|
90
|
+
array: {
|
|
91
|
+
validator: null,
|
|
92
|
+
rules: []
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const commonPropertyTypes = ['name', 'type', 'required', 'rules', 'validator', 'ref', 'default']
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Plugin factory
|
|
100
|
+
*
|
|
101
|
+
* @param {string} pkgName - NPM package name
|
|
102
|
+
* @returns {class}
|
|
103
|
+
*/
|
|
10
104
|
async function factory (pkgName) {
|
|
11
105
|
const me = this
|
|
106
|
+
const { breakNsPath } = this.app.bajo
|
|
107
|
+
|
|
108
|
+
const { find, filter, isString, map, pick, groupBy, isEmpty } = this.app.lib._
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Dobo Database Framework for {@link https://github.com/ardhi/bajo|Bajo}.
|
|
112
|
+
*
|
|
113
|
+
* See {@tutorial ecosystem} for available drivers & tools
|
|
114
|
+
*
|
|
115
|
+
* @class
|
|
116
|
+
*/
|
|
117
|
+
class Dobo extends this.app.baseClass.Base {
|
|
118
|
+
/**
|
|
119
|
+
* @constant {string[]}
|
|
120
|
+
* @memberof Dobo
|
|
121
|
+
* @default ['count', 'avg', 'min', 'max', 'sum']
|
|
122
|
+
*/
|
|
123
|
+
static aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @constant {string[]}
|
|
127
|
+
* @memberof Dobo
|
|
128
|
+
* @default ['string', 'integer', 'smallint']
|
|
129
|
+
*/
|
|
130
|
+
static idTypes = ['string', 'integer', 'smallint']
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @constant {string[]}
|
|
134
|
+
* @memberof Dobo
|
|
135
|
+
* @default ['daily', 'monthly', 'annually']
|
|
136
|
+
*/
|
|
137
|
+
static histogramTypes = ['daily', 'monthly', 'yearly']
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @constant {TPropertyType}
|
|
141
|
+
* @memberof Dobo
|
|
142
|
+
*/
|
|
143
|
+
static propertyType = propertyType
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @constant {string[]}
|
|
147
|
+
* @memberof Dobo
|
|
148
|
+
* @default ['index', 'unique', 'primary', 'fulltext']
|
|
149
|
+
*/
|
|
150
|
+
static indexTypes = ['index', 'unique', 'primary', 'fulltext']
|
|
12
151
|
|
|
13
|
-
return class Dobo extends this.lib.Plugin {
|
|
14
152
|
constructor () {
|
|
15
153
|
super(pkgName, me.app)
|
|
16
|
-
this.alias = 'db'
|
|
17
154
|
this.config = {
|
|
18
155
|
connections: [],
|
|
19
|
-
mergeProps: ['connections'],
|
|
20
156
|
validationParams: {
|
|
21
157
|
abortEarly: false,
|
|
22
158
|
convert: false,
|
|
23
159
|
allowUnknown: true
|
|
24
160
|
},
|
|
25
161
|
default: {
|
|
26
|
-
property: {
|
|
27
|
-
text: {
|
|
28
|
-
kind: 'text'
|
|
29
|
-
},
|
|
30
|
-
string: {
|
|
31
|
-
length: 50
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
162
|
filter: {
|
|
35
163
|
limit: 25,
|
|
36
164
|
maxLimit: 200,
|
|
37
165
|
hardLimit: 10000,
|
|
38
166
|
sort: ['dt:-1', 'updatedAt:-1', 'updated_at:-1', 'createdAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
|
|
39
|
-
},
|
|
40
|
-
idField: {
|
|
41
|
-
type: 'string',
|
|
42
|
-
maxLength: 50,
|
|
43
|
-
required: true,
|
|
44
|
-
index: { type: 'primary' }
|
|
45
167
|
}
|
|
46
168
|
},
|
|
47
169
|
memDb: {
|
|
48
|
-
|
|
49
|
-
persistence: {
|
|
50
|
-
syncPeriod: 1
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
this.aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
|
|
55
|
-
this.propType = {
|
|
56
|
-
integer: {
|
|
57
|
-
validator: 'number'
|
|
58
|
-
},
|
|
59
|
-
smallint: {
|
|
60
|
-
validator: 'number'
|
|
61
|
-
},
|
|
62
|
-
text: {
|
|
63
|
-
validator: 'string',
|
|
64
|
-
kind: 'text',
|
|
65
|
-
choices: ['text', 'mediumtext', 'longtext']
|
|
66
|
-
},
|
|
67
|
-
string: {
|
|
68
|
-
validator: 'string',
|
|
69
|
-
maxLength: 255,
|
|
70
|
-
minLength: 0
|
|
71
|
-
},
|
|
72
|
-
float: {
|
|
73
|
-
validator: 'number'
|
|
74
|
-
},
|
|
75
|
-
double: {
|
|
76
|
-
validator: 'number'
|
|
77
|
-
},
|
|
78
|
-
boolean: {
|
|
79
|
-
validator: 'boolean'
|
|
80
|
-
},
|
|
81
|
-
date: {
|
|
82
|
-
validator: 'date'
|
|
83
|
-
},
|
|
84
|
-
datetime: {
|
|
85
|
-
validator: 'date'
|
|
170
|
+
autoSaveDur: '1s'
|
|
86
171
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
timestamp: {
|
|
91
|
-
validator: 'timestamp'
|
|
92
|
-
},
|
|
93
|
-
object: {},
|
|
94
|
-
array: {}
|
|
172
|
+
applet: {
|
|
173
|
+
confirmation: false
|
|
174
|
+
}
|
|
95
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @type {Object[]}
|
|
179
|
+
*/
|
|
180
|
+
this.drivers = []
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @type {Object[]}
|
|
184
|
+
*/
|
|
185
|
+
this.connections = []
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @type {Object[]}
|
|
189
|
+
*/
|
|
190
|
+
this.features = []
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @type {Object[]}
|
|
194
|
+
*/
|
|
195
|
+
this.models = []
|
|
96
196
|
}
|
|
97
197
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Get allowed property keys by field type
|
|
200
|
+
*
|
|
201
|
+
* @param {string} type
|
|
202
|
+
* @returns {string[]}
|
|
203
|
+
*/
|
|
204
|
+
getPropertyKeysByType = (type) => {
|
|
205
|
+
const keys = [...commonPropertyTypes]
|
|
206
|
+
if (['string'].includes(type)) keys.push('minLength', 'maxLength', 'values')
|
|
207
|
+
if (['text'].includes(type)) keys.push('textType')
|
|
208
|
+
if (['smallint', 'integer'].includes(type)) keys.push('autoInc', 'values')
|
|
209
|
+
if (['float', 'double'].includes(type)) keys.push('values')
|
|
210
|
+
return keys
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get all allowed property keys
|
|
215
|
+
*
|
|
216
|
+
* @returns {string[]}
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
getAllPropertyKeys = (driver) => {
|
|
220
|
+
const { uniq, isEmpty } = this.app.lib._
|
|
221
|
+
const keys = [...commonPropertyTypes]
|
|
222
|
+
for (const type in propertyType) {
|
|
223
|
+
keys.push(...Object.keys(propertyType[type]))
|
|
105
224
|
}
|
|
225
|
+
if (driver && !isEmpty(driver.constructor.propertyKeys)) keys.push(...driver.constructor.propertyKeys)
|
|
226
|
+
return uniq(keys)
|
|
227
|
+
}
|
|
106
228
|
|
|
107
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Initialize plugin and performing the following tasks:
|
|
231
|
+
* - {@link module:Lib.collectDrivers|Collecting all drivers}
|
|
232
|
+
* - {@link module:Lib.collectConnections|Collecting all connections}
|
|
233
|
+
* - {@link module:Lib.collectFeatures|Collecting all features}
|
|
234
|
+
* - {@link module:Lib.collectModels|Collecting all models}
|
|
235
|
+
* @method
|
|
236
|
+
* @async
|
|
237
|
+
*/
|
|
238
|
+
init = async () => {
|
|
239
|
+
const { getPluginDataDir } = this.app.bajo
|
|
240
|
+
const { fs } = this.app.lib
|
|
108
241
|
await collectDrivers.call(this)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
this.connections = await buildCollections({ ns: this.name, container: 'connections', handler: collectConnections, dupChecks: ['name', checkType] })
|
|
116
|
-
if (this.connections.length === 0) this.log.warn('notFound%s', this.print.write('connection'))
|
|
117
|
-
await collectFeature.call(this)
|
|
118
|
-
await collectSchemas.call(this)
|
|
242
|
+
await collectConnections.call(this)
|
|
243
|
+
await collectFeatures.call(this)
|
|
244
|
+
await collectModels.call(this)
|
|
245
|
+
const attDir = `${getPluginDataDir('dobo')}/attachment`
|
|
246
|
+
fs.ensureDirSync(attDir)
|
|
119
247
|
}
|
|
120
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Start plugin
|
|
251
|
+
*
|
|
252
|
+
* @method
|
|
253
|
+
* @async
|
|
254
|
+
* @param {(string|Array)} [conns=all] - Which connections should be run on start
|
|
255
|
+
* @param {boolean} [noRebuild=true] - Set ```false``` to not rebuild model on start. Yes, only set it to ```false``` if you REALLY know what you're doing!!!
|
|
256
|
+
*/
|
|
121
257
|
start = async (conns = 'all', noRebuild = true) => {
|
|
122
|
-
const { importModule, breakNsPath } = this.app.bajo
|
|
123
|
-
const { find, filter, isString, map } = this.lib._
|
|
124
258
|
if (conns === 'all') conns = this.connections
|
|
125
259
|
else if (isString(conns)) conns = filter(this.connections, { name: conns })
|
|
126
260
|
else conns = map(conns, c => find(this.connections, { name: c }))
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await mod.call(this.app[ns], { connection: c, noRebuild, schemas })
|
|
132
|
-
this.log.trace('driverInstantiated%s%s', c.driver, c.name)
|
|
261
|
+
this.log.debug('dbInit')
|
|
262
|
+
for (const connection of conns) {
|
|
263
|
+
await connection.connect(noRebuild)
|
|
264
|
+
this.log.trace('dbInit%s%s%s', connection.driver.plugin.ns, connection.driver.name, connection.name)
|
|
133
265
|
}
|
|
134
|
-
await memDbStart.call(this)
|
|
135
266
|
}
|
|
136
267
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
for (const p of schema.properties) {
|
|
149
|
-
if (!forceNoHidden && defHidden.includes(p.name)) continue
|
|
150
|
-
result[p.name] = record[p.name] ?? null
|
|
151
|
-
if (record[p.name] === null) continue
|
|
152
|
-
switch (p.type) {
|
|
153
|
-
case 'boolean': result[p.name] = !!result[p.name]; break
|
|
154
|
-
case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
|
|
155
|
-
case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
|
|
156
|
-
case 'datetime': result[p.name] = dayjs(record[p.name]).toISOString(); break
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
result = await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
|
|
160
|
-
if (record._rel) result._rel = record._rel
|
|
161
|
-
return result
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (isEmpty(record)) return record
|
|
165
|
-
if (hidden.length > 0) record = omit(record, hidden)
|
|
166
|
-
if (!isArray(fields)) return await transform.call(this, { record, schema, hidden, forceNoHidden })
|
|
167
|
-
const fl = clone(fields)
|
|
168
|
-
if (!fl.includes('id')) fl.unshift('id')
|
|
169
|
-
if (record._rel) fl.push('_rel')
|
|
170
|
-
return pick(await transform.call(this, { record, schema, hidden, forceNoHidden }), fl)
|
|
268
|
+
/**
|
|
269
|
+
* Get connection by name. It returns the first connection named after "{name}"
|
|
270
|
+
*
|
|
271
|
+
* @param {string} name - Connection name
|
|
272
|
+
* @param {boolean} [silent] - If ```true``` and connection is not found, it won't throw error
|
|
273
|
+
* @returns {Driver} Return connection instance or ```undefined``` if silent is ```true```
|
|
274
|
+
*/
|
|
275
|
+
getConnection = (name, silent) => {
|
|
276
|
+
const conn = find(this.connections, { name })
|
|
277
|
+
if (!conn && !silent) throw this.error('unknown%s%s', this.t('connection'), name)
|
|
278
|
+
return conn
|
|
171
279
|
}
|
|
172
280
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let sort
|
|
193
|
-
if (schema && isEmpty(input)) {
|
|
194
|
-
const columns = map(schema.properties, 'name')
|
|
195
|
-
each(this.config.default.filter.sort, s => {
|
|
196
|
-
const [col] = s.split(':')
|
|
197
|
-
if (columns.includes(col)) {
|
|
198
|
-
input = s
|
|
199
|
-
return false
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
if (!isEmpty(input)) {
|
|
204
|
-
if (isPlainObject(input)) sort = input
|
|
205
|
-
else if (isString(input)) {
|
|
206
|
-
const item = {}
|
|
207
|
-
each(input.split('+'), text => {
|
|
208
|
-
let [col, dir] = map(trim(text).split(':'), i => trim(i))
|
|
209
|
-
dir = (dir ?? '').toUpperCase()
|
|
210
|
-
dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
|
|
211
|
-
item[col] = dir / Math.abs(dir)
|
|
212
|
-
})
|
|
213
|
-
sort = item
|
|
214
|
-
}
|
|
215
|
-
if (schema) {
|
|
216
|
-
const items = keys(sort)
|
|
217
|
-
each(items, i => {
|
|
218
|
-
if (!schema.sortables.includes(i) && !allowSortUnindexed) throw this.error('sortOnUnindexedField%s%s', i, schema.name)
|
|
219
|
-
// if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return sort
|
|
281
|
+
/**
|
|
282
|
+
* Get driver by name. It returns the first driver named after "{name}"
|
|
283
|
+
*
|
|
284
|
+
* Also support NsPath format for those who load the same named driver but from different provider/plugin.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} name - Driver name
|
|
287
|
+
* @param {boolean} [silent] - If ```true``` and driver is not found, it won't throw error
|
|
288
|
+
* @returns {Driver} Returns driver instance or ```undefined``` if silent is ```true```
|
|
289
|
+
*/
|
|
290
|
+
getDriver = (name, silent) => {
|
|
291
|
+
const { breakNsPath } = this.app.bajo
|
|
292
|
+
let driver
|
|
293
|
+
if (!name.includes(':')) {
|
|
294
|
+
driver = find(this.drivers, { name })
|
|
295
|
+
if (driver) return driver
|
|
296
|
+
driver = filter(this.drivers, d => d.plugin.ns === name)
|
|
297
|
+
if (driver.length === 1) return driver[0]
|
|
298
|
+
if (!silent) throw this.error('unknown%s%s', this.t('driver'), name)
|
|
299
|
+
return
|
|
224
300
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
sortInput = JSON.parse(sortInput)
|
|
230
|
-
} catch (err) {}
|
|
231
|
-
const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
|
|
232
|
-
return { limit, page, skip, sort }
|
|
301
|
+
const { ns, path } = breakNsPath(name)
|
|
302
|
+
driver = find(this.drivers, d => d.name === path && d.plugin.ns === ns)
|
|
303
|
+
if (!driver && !silent) throw this.error('unknown%s%s', this.t('driver'), name)
|
|
304
|
+
return driver
|
|
233
305
|
}
|
|
234
306
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
else if (input[0] === '{') items = JSON.parse(input)
|
|
250
|
-
else {
|
|
251
|
-
for (const item of input.split('+').map(i => i.trim())) {
|
|
252
|
-
const part = split(item, schema)
|
|
253
|
-
if (!items[part.field]) items[part.field] = []
|
|
254
|
-
items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const matcher = {}
|
|
258
|
-
for (const f of schema.fullText.fields) {
|
|
259
|
-
const value = []
|
|
260
|
-
if (typeof items[f] === 'string') items[f] = [items[f]]
|
|
261
|
-
if (Object.prototype.hasOwnProperty.call(items, f)) value.push(...items[f])
|
|
262
|
-
matcher[f] = value
|
|
263
|
-
}
|
|
264
|
-
if (Object.prototype.hasOwnProperty.call(items, '*')) matcher['*'] = items['*']
|
|
265
|
-
return matcher
|
|
307
|
+
/**
|
|
308
|
+
* Get feature by name. It returns the first driver named after "{name}"
|
|
309
|
+
*
|
|
310
|
+
* Also support NsPath format for those who load the same named driver but from different provider/plugin.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} - Feature name
|
|
313
|
+
* @returns {Feature} Return feature instance
|
|
314
|
+
*/
|
|
315
|
+
getFeature = name => {
|
|
316
|
+
if (!name.includes(':')) return find(this.features, { name })
|
|
317
|
+
const { ns, path } = breakNsPath(name)
|
|
318
|
+
const feat = find(this.features, d => d.name === path && d.plugin.ns === ns)
|
|
319
|
+
if (!feat) throw this.error('unknown%s%s', this.t('feature'), name)
|
|
320
|
+
return feat
|
|
266
321
|
}
|
|
267
322
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
})
|
|
282
|
-
const parts = fields.map(f => {
|
|
283
|
-
if (filter.query[0] === '*') return `${f}:~$'${filter.query.replaceAll('*', '')}'`
|
|
284
|
-
if (filter.query[filter.length - 1] === '*') return `${f}:~^'${filter.query.replaceAll('*', '')}'`
|
|
285
|
-
return `${f}:~'${filter.query.replaceAll('*', '')}'`
|
|
286
|
-
})
|
|
287
|
-
if (parts.length === 1) query = nql(parts[0]).parse()
|
|
288
|
-
else if (parts.length > 1) query = nql(parts.join(',')).parse()
|
|
289
|
-
}
|
|
290
|
-
} catch (err) {
|
|
291
|
-
this.error('invalidQuery', { orgMessage: err.message })
|
|
292
|
-
}
|
|
293
|
-
} else if (isPlainObject(filter.query)) query = filter.query
|
|
294
|
-
return this.sanitizeQuery(query, schema)
|
|
323
|
+
/**
|
|
324
|
+
* Get model by name
|
|
325
|
+
*
|
|
326
|
+
* @param {string} name - Model name
|
|
327
|
+
* @param {boolean} [silent] - If ```true``` and model is not found, it won't throw error
|
|
328
|
+
* @retuns {Model} Returns model instance or ```undefined``` if silent is ```true```
|
|
329
|
+
*/
|
|
330
|
+
getModel = (name, silent) => {
|
|
331
|
+
const { pascalCase } = this.app.lib.aneka
|
|
332
|
+
let model = find(this.models, { name })
|
|
333
|
+
if (!model) model = find(this.models, { name: pascalCase(name) })
|
|
334
|
+
if (!model && !silent) throw this.error('unknown%s%s', this.t('model'), name)
|
|
335
|
+
return model
|
|
295
336
|
}
|
|
296
337
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (!prop) return val
|
|
307
|
-
if (['datetime', 'date', 'time'].includes(prop.type)) {
|
|
308
|
-
const dt = dayjs(val)
|
|
309
|
-
return dt.isValid() ? dt.toDate() : val
|
|
310
|
-
} else if (['smallint', 'integer'].includes(prop.type)) return parseInt(val) || val
|
|
311
|
-
else if (['float', 'double'].includes(prop.type)) return parseFloat(val) || val
|
|
312
|
-
else if (['boolean'].includes(prop.type)) return !!val
|
|
313
|
-
return val
|
|
314
|
-
}
|
|
315
|
-
keys.forEach(k => {
|
|
316
|
-
const v = obj[k]
|
|
317
|
-
if (isPlainObject(v)) obj[k] = this.sanitizeQuery(v, schema, k)
|
|
318
|
-
else if (isArray(v)) {
|
|
319
|
-
v.forEach((i, idx) => {
|
|
320
|
-
if (isPlainObject(i)) obj[k][idx] = this.sanitizeQuery(i, schema, k)
|
|
321
|
-
})
|
|
322
|
-
} else obj[k] = sanitize(k, v, parent)
|
|
323
|
-
})
|
|
324
|
-
return obj
|
|
338
|
+
/**
|
|
339
|
+
* Get all models that bound to connection ```name```
|
|
340
|
+
*
|
|
341
|
+
* @param {string} name - Connection name
|
|
342
|
+
* @returns {Array}
|
|
343
|
+
*/
|
|
344
|
+
getModelsByConnection = name => {
|
|
345
|
+
const conn = this.getConnection(name)
|
|
346
|
+
return this.models.filter(s => s.connection.name === conn.name)
|
|
325
347
|
}
|
|
326
348
|
|
|
327
349
|
validationErrorMessage = (err) => {
|
|
@@ -335,83 +357,144 @@ async function factory (pkgName) {
|
|
|
335
357
|
return text
|
|
336
358
|
}
|
|
337
359
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Sanitize value as a date/time value. Parse/format string using {@link https://day.js.org/docs/en/display/format|dayjs format}
|
|
362
|
+
*
|
|
363
|
+
* @method
|
|
364
|
+
* @memberof Dobo
|
|
365
|
+
* @param {(number|string)} value - Value to sanitize
|
|
366
|
+
* @param {Object} [options={}] - Options object
|
|
367
|
+
* @param {boolean} [options.silent=true] - If ```true``` (default) and value isn't valid, returns empty
|
|
368
|
+
* @param {string} [options.inputFormat] - If provided, parse value using this option
|
|
369
|
+
* @param {string} [options.outputFormat] - If not provided or ```native```, returns Javascript Date. Otherwise returns formatted date/time string
|
|
370
|
+
* @returns {(string|Date)}
|
|
371
|
+
*/
|
|
372
|
+
sanitizeDate = (value, { inputFormat, outputFormat, silent = true } = {}) => {
|
|
373
|
+
const { dayjs } = this.app.lib
|
|
374
|
+
if (value === 0) return null
|
|
375
|
+
if (!outputFormat) outputFormat = inputFormat
|
|
376
|
+
const dt = dayjs(value, inputFormat)
|
|
377
|
+
if (!dt.isValid()) {
|
|
378
|
+
if (silent) return null
|
|
379
|
+
throw this.error('invalidDate')
|
|
380
|
+
}
|
|
381
|
+
if (outputFormat === 'native' || !outputFormat) return dt.toDate()
|
|
382
|
+
return dt.format(outputFormat)
|
|
341
383
|
}
|
|
342
384
|
|
|
343
|
-
|
|
344
|
-
const {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
let { ns, path: type } = breakNsPath(conn.type)
|
|
349
|
-
if (isEmpty(type)) type = conn.type
|
|
350
|
-
const driver = find(this.drivers, { type, ns, driver: conn.driver })
|
|
351
|
-
const instance = find(this.app[driver.ns].instances, { name: schema.connection })
|
|
352
|
-
const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
|
|
353
|
-
const returning = [map(schema.properties, 'name'), opts]
|
|
354
|
-
return { instance, driver, connection: conn, returning, schema }
|
|
385
|
+
sanitizeFloat = (value, { strict = false } = {}) => {
|
|
386
|
+
const { isNumber } = this.app.lib._
|
|
387
|
+
if (isNumber(value)) return value
|
|
388
|
+
if (strict) return Number(value)
|
|
389
|
+
return parseFloat(value) || null
|
|
355
390
|
}
|
|
356
391
|
|
|
357
|
-
|
|
358
|
-
const {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const schema = find(this.schemas, { name })
|
|
363
|
-
if (!schema) throw this.error('unknownModelSchema%s', name)
|
|
364
|
-
return cloned ? cloneDeep(schema) : schema
|
|
392
|
+
sanitizeInt = (value, { strict = false } = {}) => {
|
|
393
|
+
const { isNumber } = this.app.lib._
|
|
394
|
+
if (isNumber(value)) return value
|
|
395
|
+
if (strict) return Number(value)
|
|
396
|
+
return parseInt(value) || null
|
|
365
397
|
}
|
|
366
398
|
|
|
367
|
-
|
|
368
|
-
const {
|
|
369
|
-
|
|
370
|
-
|
|
399
|
+
sanitizeObject = (value) => {
|
|
400
|
+
const { isString } = this.app.lib._
|
|
401
|
+
let result = null
|
|
402
|
+
if (isString(value)) {
|
|
403
|
+
try {
|
|
404
|
+
result = JSON.parse(value)
|
|
405
|
+
} catch (err) {}
|
|
406
|
+
} else {
|
|
407
|
+
try {
|
|
408
|
+
result = JSON.parse(JSON.stringify(value))
|
|
409
|
+
} catch (err) {}
|
|
410
|
+
}
|
|
411
|
+
return result
|
|
412
|
+
}
|
|
371
413
|
|
|
372
|
-
|
|
414
|
+
sanitizeBoolean = (value) => {
|
|
415
|
+
return value === null ? null : (['true', true].includes(value))
|
|
373
416
|
}
|
|
374
417
|
|
|
375
|
-
|
|
376
|
-
|
|
418
|
+
sanitizeTimestamp = (value) => {
|
|
419
|
+
const { isNumber } = this.app.lib._
|
|
420
|
+
const { dayjs } = this.app.lib
|
|
421
|
+
if (!isNumber(value)) return -1
|
|
422
|
+
const dt = dayjs.unix(value)
|
|
423
|
+
return dt.isValid() ? dt.unix() : -1
|
|
377
424
|
}
|
|
378
425
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const all = this.memDb.storage[name] ?? []
|
|
382
|
-
if (fields.length === 0) return all
|
|
383
|
-
return map(all, item => pick(item, fields))
|
|
426
|
+
sanitizeString = (value) => {
|
|
427
|
+
return value + ''
|
|
384
428
|
}
|
|
385
429
|
|
|
386
|
-
|
|
387
|
-
const {
|
|
388
|
-
const {
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
430
|
+
_calcStats = (items, field, aggregates) => {
|
|
431
|
+
const { generateId, isSet } = this.app.lib.aneka
|
|
432
|
+
const result = { id: generateId, count: 0, avg: null, min: null, max: null }
|
|
433
|
+
let sum = 0
|
|
434
|
+
for (const item of items) {
|
|
435
|
+
const value = Number(item[field]) ?? 0
|
|
436
|
+
if (aggregates.includes('count')) result.count++
|
|
437
|
+
if (aggregates.includes('avg')) sum = sum + value
|
|
438
|
+
if (aggregates.includes('min') && (!isSet(result.min) || value < result.min)) result.min = value
|
|
439
|
+
if (aggregates.includes('max') && (!isSet(result.max) || value > result.max)) result.max = value
|
|
440
|
+
}
|
|
441
|
+
result.avg = sum / items.length
|
|
442
|
+
return result
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
calcAggregate = ({ data = [], group = '', field = '', aggregates = ['count'] } = {}) => {
|
|
446
|
+
this.checkAggregateParams({ group, field, aggregates })
|
|
447
|
+
const { pick, groupBy } = this.app.lib._
|
|
448
|
+
const grouped = groupBy(data, group)
|
|
449
|
+
const all = []
|
|
450
|
+
for (const key in grouped) {
|
|
451
|
+
const items = grouped[key]
|
|
452
|
+
const result = this._calcStats(items, field, aggregates)
|
|
453
|
+
result[group] = key
|
|
454
|
+
all.push(pick(result, ['id', group, ...aggregates]))
|
|
455
|
+
}
|
|
456
|
+
return all
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
calcHistogram = ({ data = [], type = '', group = '', field = '', aggregates = ['count'] } = {}) => {
|
|
460
|
+
this.checkHistogramParams({ group, field, type, aggregates })
|
|
461
|
+
const { dayjs } = this.app.lib
|
|
462
|
+
const pattern = { daily: ['YYYY-MM-DD', 'date'], monthly: ['YYYY-MM', 'month'], yearly: ['YYYY', 'year'] }
|
|
463
|
+
for (const d of data) {
|
|
464
|
+
d._group = dayjs(d[group]).format(pattern[type][0])
|
|
465
|
+
}
|
|
466
|
+
const grouped = groupBy(data, '_group')
|
|
467
|
+
const all = []
|
|
468
|
+
for (const key in grouped) {
|
|
469
|
+
const items = grouped[key]
|
|
470
|
+
const result = this._calcStats(items, field, aggregates)
|
|
471
|
+
const title = pattern[type][1]
|
|
472
|
+
result[title] = key
|
|
473
|
+
all.push(pick(result, ['id', title, ...aggregates]))
|
|
474
|
+
}
|
|
475
|
+
return all
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
checkAggregateParams = (params = {}) => {
|
|
479
|
+
let { group, field, aggregates } = params
|
|
480
|
+
if (isString(aggregates)) aggregates = [aggregates]
|
|
481
|
+
params.aggregates = aggregates
|
|
482
|
+
if (isEmpty(group)) throw this.error('fieldGroupMissing')
|
|
483
|
+
if (isEmpty(field)) throw this.error('fieldCalcMissing')
|
|
484
|
+
for (const agg of aggregates) {
|
|
485
|
+
if (!this.constructor.aggregateTypes.includes(agg)) throw this.error('unsupportedAggregateType%s', agg)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
checkHistogramParams = (params = {}) => {
|
|
490
|
+
this.checkAggregateParams(params)
|
|
491
|
+
const { type } = params
|
|
492
|
+
if (isEmpty(type)) throw this.error('histogramTypeMissing')
|
|
493
|
+
if (!this.constructor.histogramTypes.includes(type)) throw this.error('unsupportedHistogramType%s', type)
|
|
413
494
|
}
|
|
414
495
|
}
|
|
496
|
+
|
|
497
|
+
return Dobo
|
|
415
498
|
}
|
|
416
499
|
|
|
417
500
|
export default factory
|