dobo 2.30.7 → 2.32.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/.jsdoc.conf.json +1 -1
- package/docs/Dobo.html +1 -24
- package/docs/DoboAction.html +13 -0
- package/docs/DoboAdapter.html +3 -0
- package/docs/DoboConnection.html +3 -0
- package/docs/DoboFeature.html +3 -0
- package/docs/DoboModel.html +51 -0
- package/docs/data/search.json +1 -1
- package/docs/extend_bajo_hook-docs.js.html +241 -0
- package/docs/external-Tools.html +3 -0
- package/docs/global.html +1 -5
- package/docs/index.html +1 -1
- package/docs/index.js.html +434 -380
- package/docs/lib_factory_action.js.html +290 -0
- package/docs/lib_factory_adapter.js.html +1057 -0
- package/docs/lib_factory_connection.js.html +112 -0
- package/docs/lib_factory_feature.js.html +55 -0
- package/docs/lib_factory_model.js.html +503 -0
- package/docs/lib_factory_model_build.js.html +28 -0
- package/docs/lib_factory_model_clear-record.js.html +33 -0
- package/docs/lib_factory_model_count-record.js.html +45 -0
- package/docs/lib_factory_model_create-record.js.html +52 -0
- package/docs/lib_factory_model_drop.js.html +29 -0
- package/docs/lib_factory_model_exists.js.html +29 -0
- package/docs/lib_factory_model_find-all-record.js.html +115 -0
- package/docs/lib_factory_model_find-one-record.js.html +41 -0
- package/docs/lib_factory_model_find-record.js.html +85 -0
- package/docs/lib_factory_model_get-record.js.html +55 -0
- package/docs/lib_factory_model_helper.js.html +579 -0
- package/docs/lib_factory_model_remove-record.js.html +47 -0
- package/docs/lib_factory_model_sanitize-body.js.html +60 -0
- package/docs/lib_factory_model_sanitize-fixture.js.html +59 -0
- package/docs/lib_factory_model_sanitize-record.js.html +73 -0
- package/docs/lib_factory_model_update-record.js.html +61 -0
- package/docs/lib_factory_model_upsert-record.js.html +74 -0
- package/docs/{method_validate.js.html → lib_factory_model_validate.js.html} +109 -73
- package/docs/lib_helper.js.html +466 -0
- package/docs/module-Helper.html +3 -0
- package/docs/module-Helper_Model.html +3 -0
- package/docs/module-Hook.html +15 -0
- package/docs/scripts/core.js +477 -476
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +285 -282
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1202 -1195
- 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 +509 -508
- package/docs/static/home.md +3 -1
- package/extend/bajo/hook-docs.js +238 -0
- package/extend/bajo/intl/en-US.json +9 -9
- package/extend/bajo/intl/id.json +9 -9
- package/extend/bajoCli/applet/rebuild-model.js +2 -2
- package/extend/dobo/{driver → adapter}/memory.js +6 -6
- package/extend/dobo/feature/immutable.js +2 -2
- package/extend/dobo/feature/removed-at.js +1 -1
- package/index.js +33 -30
- package/lib/factory/action.js +145 -18
- package/lib/factory/adapter.js +1054 -0
- package/lib/factory/connection.js +46 -22
- package/lib/factory/feature.js +23 -3
- package/lib/factory/model/build.js +11 -2
- package/lib/factory/model/bulk-create-record.js +2 -2
- package/lib/factory/model/clear-record.js +13 -3
- package/lib/factory/model/count-record.js +13 -2
- package/lib/factory/model/create-aggregate.js +2 -2
- package/lib/factory/model/create-attachment.js +1 -1
- package/lib/factory/model/create-histogram.js +2 -2
- package/lib/factory/model/create-record.js +15 -2
- package/lib/factory/model/drop.js +12 -2
- package/lib/factory/model/exists.js +10 -6
- package/lib/factory/model/find-all-record.js +14 -3
- package/lib/factory/model/find-attachment.js +1 -1
- package/lib/factory/model/find-one-record.js +13 -1
- package/lib/factory/model/find-record.js +17 -53
- package/lib/factory/model/get-record.js +11 -39
- package/lib/factory/model/{_util.js → helper.js} +176 -16
- package/lib/factory/model/remove-attachment.js +1 -1
- package/lib/factory/model/remove-record.js +11 -28
- package/lib/factory/model/sanitize-body.js +14 -12
- package/lib/factory/model/sanitize-fixture.js +17 -4
- package/lib/factory/model/sanitize-record.js +9 -6
- package/lib/factory/model/transaction.js +2 -2
- package/lib/factory/model/update-record.js +12 -40
- package/lib/factory/model/upsert-record.js +13 -42
- package/lib/factory/model/validate.js +47 -14
- package/lib/factory/model.js +301 -23
- package/lib/{collect-models.js → helper.js} +463 -364
- package/package.json +1 -1
- package/wiki/CHANGES.md +17 -7
- package/wiki/ECOSYSTEM.md +7 -7
- package/wiki/GETTING-STARTED.md +3 -3
- package/wiki/QUERY-LANGUAGE.md +1 -1
- package/docs/lib_collect-connections.js.html +0 -39
- package/docs/lib_collect-drivers.js.html +0 -52
- package/docs/lib_collect-features.js.html +0 -36
- package/docs/lib_collect-schemas.js.html +0 -94
- package/docs/lib_index.js.html +0 -6
- package/docs/method_model_create.js.html +0 -35
- package/docs/method_model_drop.js.html +0 -34
- package/docs/method_model_exists.js.html +0 -40
- package/docs/method_record_count.js.html +0 -69
- package/docs/method_record_create.js.html +0 -114
- package/docs/method_record_find-all.js.html +0 -44
- package/docs/method_record_find-one.js.html +0 -73
- package/docs/method_record_find.js.html +0 -118
- package/docs/method_record_get.js.html +0 -92
- package/docs/method_record_remove.js.html +0 -75
- package/docs/method_record_update.js.html +0 -107
- package/docs/method_record_upsert.js.html +0 -54
- package/docs/method_sanitize_body.js.html +0 -88
- package/docs/method_sanitize_date.js.html +0 -30
- package/docs/method_sanitize_id.js.html +0 -20
- package/docs/module-Lib.html +0 -3
- package/lib/collect-connections.js +0 -56
- package/lib/collect-drivers.js +0 -35
- package/lib/collect-features.js +0 -40
- package/lib/factory/driver.js +0 -574
- package/lib/factory/model/sanitize-id.js +0 -7
- package/lib/index.js +0 -3
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
import { ulid } from 'ulid'
|
|
2
|
+
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid'
|
|
3
|
+
import crypto from 'crypto'
|
|
4
|
+
|
|
5
|
+
const defIdField = {
|
|
6
|
+
name: '_id',
|
|
7
|
+
type: 'string',
|
|
8
|
+
maxLength: 50,
|
|
9
|
+
required: true,
|
|
10
|
+
index: 'primary'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @external Tools
|
|
15
|
+
* @see {@link https://ardhi.github.io/bajo/Tools.html|Bajo Tools}
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef TIdField
|
|
20
|
+
* @type {object}
|
|
21
|
+
* @memberof DoboAdapter
|
|
22
|
+
* @property {string} [name='_id'] - The name of the ID field.
|
|
23
|
+
* @property {string} [type='string'] - The data type of the ID field.
|
|
24
|
+
* @property {number} [maxLength=50] - The maximum length of the ID field.
|
|
25
|
+
* @property {boolean} [required=true] - Indicates if the ID field is required.
|
|
26
|
+
* @property {string} [index='primary'] - The index type of the ID field.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef TSupport
|
|
31
|
+
* @memberof DoboAdapter
|
|
32
|
+
* @type {object}
|
|
33
|
+
* @property {object} [propType={}] - An object indicating support for various property types.
|
|
34
|
+
* @property {boolean} [propType.object=false] - Indicates if object property type is supported.
|
|
35
|
+
* @property {boolean} [propType.array=false] - Indicates if array property type is supported.
|
|
36
|
+
* @property {boolean} [propType.datetime=true] - Indicates if datetime property type is supported.
|
|
37
|
+
* @property {boolean} [search=false] - Indicates if search functionality is supported.
|
|
38
|
+
* @property {boolean} [uniqueIndex=false] - Indicates if unique index functionality is supported.
|
|
39
|
+
* @property {boolean} [nullableField=true] - Indicates if nullable fields are supported.
|
|
40
|
+
* @property {boolean} [transaction=false] - Indicates if transaction functionality is supported.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Adapter factory function.
|
|
45
|
+
*
|
|
46
|
+
* @async
|
|
47
|
+
* @returns {Promise<DoboAdapter>}
|
|
48
|
+
*/
|
|
49
|
+
async function adapterFactory () {
|
|
50
|
+
const { Tools } = this.app.baseClass
|
|
51
|
+
const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction, camelCase, last } = this.app.lib._
|
|
52
|
+
const { isSet } = this.app.lib.aneka
|
|
53
|
+
const { runHook } = this.app.bajo
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* DoboAdapter class serves as a base class for all database adapters in the Dobo framework. It provides common functionality for managing models, records, and database operations.
|
|
57
|
+
* Child classes should implement the abstract methods to provide specific database functionality.
|
|
58
|
+
*
|
|
59
|
+
* @class
|
|
60
|
+
* @extends external:Tools
|
|
61
|
+
*/
|
|
62
|
+
class DoboAdapter extends Tools {
|
|
63
|
+
/**
|
|
64
|
+
* Constructor.
|
|
65
|
+
*/
|
|
66
|
+
constructor (plugin, name, options = {}) {
|
|
67
|
+
super(plugin)
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Adapter name
|
|
71
|
+
* @type {string}
|
|
72
|
+
*/
|
|
73
|
+
this.name = name
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ID field configuration
|
|
77
|
+
* @type {DoboAdapter.TIdField}
|
|
78
|
+
*/
|
|
79
|
+
this.idField = cloneDeep(defIdField)
|
|
80
|
+
this.propertyType = {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Support configuration for the adapter
|
|
84
|
+
* @type {DoboAdapter.TSupport}
|
|
85
|
+
*/
|
|
86
|
+
this.support = {
|
|
87
|
+
propType: {
|
|
88
|
+
object: false,
|
|
89
|
+
array: false,
|
|
90
|
+
datetime: true
|
|
91
|
+
},
|
|
92
|
+
search: false,
|
|
93
|
+
uniqueIndex: false,
|
|
94
|
+
nullableField: true,
|
|
95
|
+
transaction: false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Indicates whether to use UTC for datetime fields
|
|
100
|
+
* @type {boolean}
|
|
101
|
+
*/
|
|
102
|
+
this.useUtc = false
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Maximum chunk size for bulk operations
|
|
106
|
+
* @type {number}
|
|
107
|
+
*/
|
|
108
|
+
this.maxChunkSize = 500
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Indicates whether the adapter uses in-memory storage
|
|
112
|
+
* @type {boolean}
|
|
113
|
+
*/
|
|
114
|
+
this.memory = false
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adapter options
|
|
118
|
+
* @type {object}
|
|
119
|
+
*/
|
|
120
|
+
this.options = options
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sanitize connection object
|
|
125
|
+
* @async
|
|
126
|
+
* @method
|
|
127
|
+
* @param {Object} conn - Connection object
|
|
128
|
+
* @returns {Promise<void>}
|
|
129
|
+
*/
|
|
130
|
+
async sanitizeConnection (conn) {
|
|
131
|
+
conn.proto = conn.proto ?? 'http' // used by adapter that use url based connection
|
|
132
|
+
conn.memory = false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sanitizes the body of a record before creating or updating it. It ensures that all required fields
|
|
137
|
+
* are present and have valid values, and converts data types as necessary.
|
|
138
|
+
* @param {DoboModel} model - The model instance for which the body is being sanitized
|
|
139
|
+
* @param {object} body - The body of the record to be sanitized
|
|
140
|
+
* @param {boolean} [partial=false] - Indicates whether to perform a partial update
|
|
141
|
+
* @returns {object} - Sanitized body
|
|
142
|
+
*/
|
|
143
|
+
sanitizeBody (model, body = {}, partial) {
|
|
144
|
+
const { keys, pick } = this.app.lib._
|
|
145
|
+
const item = cloneDeep(body)
|
|
146
|
+
let newId = false
|
|
147
|
+
if (has(item, 'id') && this.idField.name !== 'id') {
|
|
148
|
+
item[this.idField.name] = item.id
|
|
149
|
+
newId = true
|
|
150
|
+
}
|
|
151
|
+
for (const prop of model.getNonVirtualProperties()) {
|
|
152
|
+
if (item[prop.name] === 'null') item[prop.name] = null
|
|
153
|
+
if (!isSet(item[prop.name]) && !this.support.nullableField) {
|
|
154
|
+
switch (prop.type) {
|
|
155
|
+
case 'datetime': item[prop.name] = new Date(0); break
|
|
156
|
+
case 'float':
|
|
157
|
+
case 'double': item[prop.name] = 0; break
|
|
158
|
+
case 'string':
|
|
159
|
+
case 'text': item[prop.name] = ''; break
|
|
160
|
+
case 'object': item[prop.name] = {}; break
|
|
161
|
+
case 'array': item[prop.name] = []; break
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (isSet(item[prop.name]) && !this.support.propType[prop.type]) {
|
|
165
|
+
if (prop.type === 'datetime') item[prop.name] = item[prop.name].toISOString()
|
|
166
|
+
else if (['object', 'array'].includes(prop.type)) item[prop.name] = JSON.stringify(item[prop.name])
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const result = partial ? pick(item, keys(body)) : item
|
|
170
|
+
if (newId) delete result.id
|
|
171
|
+
return result
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Sanitizes a record retrieved from the database, converting data types as necessary
|
|
176
|
+
* and ensuring that the record conforms to the model's schema.
|
|
177
|
+
* @param {DoboModel} model - The model instance for which the record is being sanitized
|
|
178
|
+
* @param {object} record - The record retrieved from the database
|
|
179
|
+
* @returns {object} - Sanitized record
|
|
180
|
+
*/
|
|
181
|
+
sanitizeRecord (model, record = {}) {
|
|
182
|
+
const { dayjs } = this.app.lib
|
|
183
|
+
const { isString } = this.app.lib._
|
|
184
|
+
const item = { ...record }
|
|
185
|
+
if (has(item, this.idField.name) && this.idField.name !== 'id') {
|
|
186
|
+
item.id = item[this.idField.name]
|
|
187
|
+
delete item[this.idField.name]
|
|
188
|
+
}
|
|
189
|
+
for (const prop of model.properties) {
|
|
190
|
+
if (isSet(item[prop.name])) {
|
|
191
|
+
if (!this.support.propType[prop.type]) {
|
|
192
|
+
try {
|
|
193
|
+
if (prop.type === 'datetime') {
|
|
194
|
+
const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
|
|
195
|
+
item[prop.name] = dt.toDate()
|
|
196
|
+
} else if (['object', 'array'].includes(prop.type)) item[prop.name] = JSON.parse(item[prop.name])
|
|
197
|
+
} catch (err) {
|
|
198
|
+
item[prop.name] = null
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (prop.type === 'datetime' && isString(item[prop.name])) {
|
|
202
|
+
const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
|
|
203
|
+
item[prop.name] = dt.toDate()
|
|
204
|
+
}
|
|
205
|
+
if (prop.type === 'boolean' && isSet(item[prop.name])) item[prop.name] = Boolean(item[prop.name])
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return item
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Utility method to get the real fields of a model, excluding virtual fields.
|
|
213
|
+
* This is useful for operations that require only the actual stored properties of a model.
|
|
214
|
+
* @param {*} model
|
|
215
|
+
* @returns {string[]} - Array of real field names
|
|
216
|
+
*/
|
|
217
|
+
getRealFields (model) {
|
|
218
|
+
return model.getProperties({ noVirtual: true, namesOnly: true })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Utility method to get the virtual fields of a model.
|
|
223
|
+
* This is useful for operations that need to work with computed or derived properties.
|
|
224
|
+
* @param {DoboModel} model - The model instance
|
|
225
|
+
* @returns {string[]} - Array of virtual field names
|
|
226
|
+
*/
|
|
227
|
+
getVirtualFields (model) {
|
|
228
|
+
return model.getVirtualProperties({ namesOnly: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get returning fields for a model based on the provided options. If the adapter supports returning fields,
|
|
233
|
+
* it will return the specified fields or all model properties. It ensures that the ID field is always
|
|
234
|
+
* included in the returned fields.
|
|
235
|
+
* @param {DoboModel} model - The model instance for which to get the returning fields
|
|
236
|
+
* @param {object} options - Options that may include the fields to return
|
|
237
|
+
* @returns {string[]} - Array of field names to be returned
|
|
238
|
+
*/
|
|
239
|
+
_getReturningFields (model, options = {}) {
|
|
240
|
+
const { fields = [] } = options
|
|
241
|
+
if (!this.support.returning) return []
|
|
242
|
+
let items = fields.length > 0 ? [...fields] : model.properties.map(prop => prop.name)
|
|
243
|
+
if (!items.includes(this.idField.name)) items.unshift(this.idField.name)
|
|
244
|
+
if (this.idField.name !== 'id') items = without(items, ['id'])
|
|
245
|
+
return uniq(items)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Attaches hooks to the model for various operations. It runs the appropriate hooks
|
|
250
|
+
* before and after the specified operation, allowing for custom behavior to be injected
|
|
251
|
+
* into the model's lifecycle.
|
|
252
|
+
* @internal
|
|
253
|
+
* @async
|
|
254
|
+
* @method
|
|
255
|
+
* @param {string} name - The name of the hook
|
|
256
|
+
* @param {DoboModel} model - The model instance to which the hook is being attached
|
|
257
|
+
* @param {...any} args - Additional arguments to be passed to the hook
|
|
258
|
+
*/
|
|
259
|
+
async _attachHook (name, model, ...args) {
|
|
260
|
+
const { ns } = this.app.dobo
|
|
261
|
+
const { kebabCase } = this.app.lib._
|
|
262
|
+
const options = last(args)
|
|
263
|
+
if (!options.noAdapterHook) {
|
|
264
|
+
const prefix = kebabCase(name).split('-')[0]
|
|
265
|
+
await runHook(`${ns}.adapter:${prefix}Any`, model, options)
|
|
266
|
+
await runHook(`${ns}.adapter:${name}`, model, ...args)
|
|
267
|
+
await runHook(`${ns}.adapter.${camelCase(model.name)}:${name}`, ...args)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Checks the uniqueness of fields with a unique index.
|
|
273
|
+
* @async
|
|
274
|
+
* @method
|
|
275
|
+
* @internal
|
|
276
|
+
* @param {DoboModel} model - The model instance to check
|
|
277
|
+
* @param {object} body - The data to be checked for uniqueness
|
|
278
|
+
* @param {object} options - Additional options, including the action being performed
|
|
279
|
+
* @returns {Promise<void>} - Resolves if unique, throws an error if not
|
|
280
|
+
*/
|
|
281
|
+
_checkUnique = async (model, body = {}, options = {}) => {
|
|
282
|
+
const { isSet } = this.app.lib.aneka
|
|
283
|
+
const { filter, map, isEmpty, forOwn } = this.app.lib._
|
|
284
|
+
const indexes = filter(model.indexes ?? [], idx => idx.type === 'unique')
|
|
285
|
+
for (const index of indexes) {
|
|
286
|
+
const query = {}
|
|
287
|
+
for (const field of index.fields) {
|
|
288
|
+
if (isSet(body[field])) query[field] = body[field]
|
|
289
|
+
}
|
|
290
|
+
if (isEmpty(query)) continue
|
|
291
|
+
const { data } = await model.findOneRecord({ query }, options)
|
|
292
|
+
if (!isEmpty(data)) {
|
|
293
|
+
if (['updateRecord', 'upsertRecord'].includes(options.action)) {
|
|
294
|
+
let eq = true
|
|
295
|
+
forOwn(query, (v, k) => {
|
|
296
|
+
if (data[k] !== v) eq = false
|
|
297
|
+
})
|
|
298
|
+
if (!eq) continue
|
|
299
|
+
}
|
|
300
|
+
const error = this.app.dobo.t('uniqueConstraintError')
|
|
301
|
+
const details = map(index.fields, field => {
|
|
302
|
+
return { field, error }
|
|
303
|
+
})
|
|
304
|
+
throw this.app.dobo.error(error, { details, body })
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Internal calls that will be called by model
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Wrapper for the `modelExists` method, called internally by `model` to make sure
|
|
313
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
314
|
+
*
|
|
315
|
+
* @internal
|
|
316
|
+
* @async
|
|
317
|
+
* @method
|
|
318
|
+
* @param {DoboModel} model
|
|
319
|
+
* @param {object} options
|
|
320
|
+
* @returns {Promise<boolean>}
|
|
321
|
+
*/
|
|
322
|
+
async _modelExists (model, options = {}) {
|
|
323
|
+
return await this.modelExists(model, options)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Wrapper for the `buildModel` method, called internally by `model` to make sure
|
|
328
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
329
|
+
* @internal
|
|
330
|
+
* @async
|
|
331
|
+
* @method
|
|
332
|
+
* @param {DoboModel} model
|
|
333
|
+
* @param {object} options
|
|
334
|
+
* @returns {Promise<object>}
|
|
335
|
+
*/
|
|
336
|
+
async _buildModel (model, options = {}) {
|
|
337
|
+
return await this.buildModel(model, options)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Wrapper for the `dropModel` method, called internally by `model` to make sure
|
|
342
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
343
|
+
* @internal
|
|
344
|
+
* @async
|
|
345
|
+
* @method
|
|
346
|
+
* @param {DoboModel} model
|
|
347
|
+
* @param {object} options
|
|
348
|
+
* @returns {Promise<object>}
|
|
349
|
+
*/
|
|
350
|
+
async _dropModel (model, options = {}) {
|
|
351
|
+
return await this.dropModel(model, options)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Prepares the body of a record for creation by populating default values for properties
|
|
356
|
+
* that are not set. It handles various types of default values, including functions,
|
|
357
|
+
* special strings (like 'now', 'uuid', etc.), and static values.
|
|
358
|
+
* @internal
|
|
359
|
+
* @async
|
|
360
|
+
* @method
|
|
361
|
+
* @param {DoboModel} model - The model instance for which the body is being prepared
|
|
362
|
+
* @param {object} body - The data to be prepared for creation
|
|
363
|
+
* @param {object} options - Additional options that may affect the preparation
|
|
364
|
+
* @returns {Promise<object>} - The prepared body with default values populated
|
|
365
|
+
*/
|
|
366
|
+
async _prepBodyForCreate (model, body = {}, options = {}) {
|
|
367
|
+
const { callHandler } = this.app.bajo
|
|
368
|
+
const { isSet, generateId } = this.app.lib.aneka
|
|
369
|
+
for (const prop of model.getProperties({ noVirtual: true })) {
|
|
370
|
+
if (isSet(prop.default) && (!options.noDefault) && (!isSet(body[prop.name]) || body[prop.name] === prop.default)) {
|
|
371
|
+
if (isFunction(prop.default)) body[prop.name] = await prop.default.call(model)
|
|
372
|
+
else if (typeof prop.default !== 'string') body[prop.name] = prop.default
|
|
373
|
+
else {
|
|
374
|
+
if (['now'].includes(prop.default) && prop.type === 'datetime') {
|
|
375
|
+
body[prop.name] = new Date()
|
|
376
|
+
} else if (['uuid', 'uuidv4'].includes(prop.default) && prop.type === 'string') {
|
|
377
|
+
body[prop.name] = uuidv4().slice(0, prop.maxLength)
|
|
378
|
+
} else if (prop.default === 'uuidv7' && prop.type === 'string') {
|
|
379
|
+
body[prop.name] = uuidv7().slice(0, prop.maxLength)
|
|
380
|
+
} else if (prop.default === 'ulid' && prop.type === 'string') {
|
|
381
|
+
body[prop.name] = ulid().slice(0, prop.maxLength)
|
|
382
|
+
} else if (prop.default === 'generateid' && prop.type === 'string') {
|
|
383
|
+
body[prop.name] = generateId()
|
|
384
|
+
} else if (prop.default.startsWith('handler:')) {
|
|
385
|
+
const [, ...args] = prop.default.split(':')
|
|
386
|
+
if (args.length > 0) body[prop.name] = await callHandler(args.join(':'))
|
|
387
|
+
} else if (prop.default.startsWith('md5:') && prop.type === 'string') {
|
|
388
|
+
const [, field] = prop.default.split(':')
|
|
389
|
+
const fields = field.split(',')
|
|
390
|
+
if (model.properties.filter(item => fields.includes(item.name)).length === fields.length) {
|
|
391
|
+
const values = fields.map(f => body[f])
|
|
392
|
+
body[prop.name] = crypto.createHash('md5').update(values.join(':')).digest('hex')
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
body[prop.name] = prop.default
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return pick(body, this.getRealFields(model))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Prepares the ID for a record before creation. It generates an ID if it is not set in the body.
|
|
405
|
+
*
|
|
406
|
+
* @internal
|
|
407
|
+
* @async
|
|
408
|
+
* @method
|
|
409
|
+
* @param {DoboModel} model - The model instance for which the ID is being prepared
|
|
410
|
+
* @param {object} body - The data containing the ID
|
|
411
|
+
* @param {object} options - Additional options that may affect ID generation
|
|
412
|
+
* @returns {Promise<void>} - Resolves when the ID has been prepared
|
|
413
|
+
*/
|
|
414
|
+
async _prepIdForCreate (model, body = {}, options = {}) {
|
|
415
|
+
const { isSet, generateId } = this.app.lib.aneka
|
|
416
|
+
const { isFunction } = this.app.lib._
|
|
417
|
+
const prop = model.properties.find(p => p.name === 'id')
|
|
418
|
+
if (!isSet(body.id) && prop.type === 'string') {
|
|
419
|
+
if (this.idGenerator) {
|
|
420
|
+
if (['uuid', 'uuidv4'].includes(this.idGenerator)) body.id = uuidv4()
|
|
421
|
+
else if (['uuidv7'].includes(this.idGenerator)) body.id = uuidv7()
|
|
422
|
+
else if (this.idGenerator === 'generateId') body.id = generateId()
|
|
423
|
+
else if (isFunction(this.idGenerator)) body.id = await this.idGenerator(model, body, options)
|
|
424
|
+
}
|
|
425
|
+
if (!body.id) body.id = ulid()
|
|
426
|
+
body.id = body.id.slice(0, prop.maxLength)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
_injectMeta (result = {}, options = {}) {
|
|
431
|
+
result.warnings = result.warnings ?? []
|
|
432
|
+
result.warnings.push(...(options.warnings ?? []))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Wrapper for the {@link DoboAdapter#createRecord} method, called internally by `model` to make sure
|
|
437
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
438
|
+
* @internal
|
|
439
|
+
* @async
|
|
440
|
+
* @method
|
|
441
|
+
* @param {DoboModel} model
|
|
442
|
+
* @param {object} input
|
|
443
|
+
* @param {object} options
|
|
444
|
+
* @returns {Promise<object>}
|
|
445
|
+
*/
|
|
446
|
+
async _createRecord (model, input = {}, options = {}) {
|
|
447
|
+
const { isSet } = this.app.lib.aneka
|
|
448
|
+
let body = await this._prepBodyForCreate(model, input, options)
|
|
449
|
+
await this._prepIdForCreate(model, body, options)
|
|
450
|
+
if (!options.noUniqueCheck) {
|
|
451
|
+
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
452
|
+
}
|
|
453
|
+
if (!options.noIdCheck && isSet(body.id)) {
|
|
454
|
+
const resp = await this.getRecord(model, body.id, { noMagic: true })
|
|
455
|
+
if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
|
|
456
|
+
}
|
|
457
|
+
body = this.sanitizeBody(model, body)
|
|
458
|
+
|
|
459
|
+
await this._attachHook('beforeCreateRecord', model, body, options)
|
|
460
|
+
const result = await this.createRecord(model, body, options)
|
|
461
|
+
await this._attachHook('afterCreateRecord', model, body, result, options)
|
|
462
|
+
|
|
463
|
+
if (options.noResult) return
|
|
464
|
+
result.data = this.sanitizeRecord(model, result.data)
|
|
465
|
+
this._injectMeta(result, options)
|
|
466
|
+
return result
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Wrapper for the {@link DoboAdapter#bulkCreateRecord} method, called internally by `model` to make sure
|
|
471
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
472
|
+
* @internal
|
|
473
|
+
* @async
|
|
474
|
+
* @method
|
|
475
|
+
* @param {DoboModel} model
|
|
476
|
+
* @param {Array<object>} bodies
|
|
477
|
+
* @param {object} options
|
|
478
|
+
* @returns {Promise<void>}
|
|
479
|
+
*/
|
|
480
|
+
async _bulkCreateRecord (model, bodies = [], options = {}) {
|
|
481
|
+
const { chunk } = this.app.lib._
|
|
482
|
+
let { chunkSize = this.maxChunkSize } = options
|
|
483
|
+
if (chunkSize > this.maxChunkSize) chunkSize = this.maxChunkSize
|
|
484
|
+
for (const idx in bodies) {
|
|
485
|
+
const body = await this._prepBodyForCreate(model, bodies[idx], options)
|
|
486
|
+
await this._prepIdForCreate(model, body, options)
|
|
487
|
+
bodies[idx] = this.sanitizeBody(model, body)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await this._attachHook('beforeBulkCreateRecord', model, bodies, options)
|
|
491
|
+
const items = chunk(bodies, chunkSize)
|
|
492
|
+
for (const item of items) {
|
|
493
|
+
await this.bulkCreateRecord(model, item, options)
|
|
494
|
+
}
|
|
495
|
+
await this._attachHook('afterBulkCreateRecord', model, bodies, [], options)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Wrapper for the {@link DoboAdapter#getRecord} method, called internally by `model` to make sure
|
|
500
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
501
|
+
* @internal
|
|
502
|
+
* @async
|
|
503
|
+
* @method
|
|
504
|
+
* @param {DoboModel} model
|
|
505
|
+
* @param {string|number} id
|
|
506
|
+
* @param {object} options
|
|
507
|
+
* @returns {Promise<object>}
|
|
508
|
+
*/
|
|
509
|
+
async _getRecord (model, id, options = {}) {
|
|
510
|
+
await this._attachHook('beforeGetRecord', model, id, options)
|
|
511
|
+
const result = await this.getRecord(model, id, options)
|
|
512
|
+
await this._attachHook('afterGetRecord', model, id, result, options)
|
|
513
|
+
|
|
514
|
+
if (isEmpty(result.data) && options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
515
|
+
result.data = this.sanitizeRecord(model, result.data)
|
|
516
|
+
this._injectMeta(result, options)
|
|
517
|
+
return result
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Wrapper for the {@link DoboAdapter#updateRecord} method, called internally by `model` to make sure
|
|
522
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
523
|
+
* @internal
|
|
524
|
+
* @async
|
|
525
|
+
* @method
|
|
526
|
+
* @param {DoboModel} model
|
|
527
|
+
* @param {string|number} id
|
|
528
|
+
* @param {object} input
|
|
529
|
+
* @param {object} options
|
|
530
|
+
* @returns {Promise<object>}
|
|
531
|
+
*/
|
|
532
|
+
async _updateRecord (model, id, input = {}, options = {}) {
|
|
533
|
+
let body = omit(input, this.getVirtualFields(model))
|
|
534
|
+
if (!options.noUniqueCheck) {
|
|
535
|
+
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
536
|
+
}
|
|
537
|
+
if (!options._data) {
|
|
538
|
+
const resp = await this.getRecord(model, id, { noMagic: true })
|
|
539
|
+
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
540
|
+
options._data = resp.data
|
|
541
|
+
}
|
|
542
|
+
body = this.sanitizeBody(model, body, true)
|
|
543
|
+
delete body.id
|
|
544
|
+
|
|
545
|
+
await this._attachHook('beforeUpdateRecord', model, id, body, options)
|
|
546
|
+
const result = await this.updateRecord(model, id, body, options)
|
|
547
|
+
await this._attachHook('afterUpdateRecord', model, id, body, result, options)
|
|
548
|
+
|
|
549
|
+
if (options.noResult) return
|
|
550
|
+
result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
551
|
+
result.data = this.sanitizeRecord(model, result.data)
|
|
552
|
+
this._injectMeta(result, options)
|
|
553
|
+
return result
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Upserts a record for the given model.
|
|
558
|
+
* This method will only run if child adapter does not implement {@link DoboAdapter#upsertRecord}.
|
|
559
|
+
*
|
|
560
|
+
* @internal
|
|
561
|
+
* @async
|
|
562
|
+
* @method
|
|
563
|
+
* @param {DoboModel} model - The model instance for which the record is being upserted
|
|
564
|
+
* @param {object} input - The input data for the upsert
|
|
565
|
+
* @param {object} options - Additional options that may affect record upserting
|
|
566
|
+
* @returns {Promise<object>} - The result of the record upsert
|
|
567
|
+
*/
|
|
568
|
+
async _upsertRecord (model, input = {}, options = {}) {
|
|
569
|
+
let body = omit(input, this.getVirtualFields(model))
|
|
570
|
+
if (!options.noUniqueCheck) {
|
|
571
|
+
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
572
|
+
}
|
|
573
|
+
if (isSet(body.id)) {
|
|
574
|
+
if (!options._data) {
|
|
575
|
+
const resp = await this.getRecord(model, body.id, { noMagic: true })
|
|
576
|
+
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
|
|
577
|
+
options._data = resp.data
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
body = this.sanitizeBody(model, body)
|
|
581
|
+
|
|
582
|
+
await this._attachHook('beforeUpsertRecord', model, body, options)
|
|
583
|
+
const result = await this.upsertRecord(model, body, options)
|
|
584
|
+
await this._attachHook('afterUpsertRecord', model, body, result, options)
|
|
585
|
+
|
|
586
|
+
if (options.noResult) return
|
|
587
|
+
if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
588
|
+
result.data = this.sanitizeRecord(model, result.data)
|
|
589
|
+
this._injectMeta(result, options)
|
|
590
|
+
return result
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Wrapper for the {@link DoboAdapter#removeRecord} method, called internally by `model` to make sure
|
|
595
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
596
|
+
* @internal
|
|
597
|
+
* @async
|
|
598
|
+
* @method
|
|
599
|
+
* @param {DoboModel} model
|
|
600
|
+
* @param {string|number} id
|
|
601
|
+
* @param {object} options
|
|
602
|
+
* @returns {Promise<object>}
|
|
603
|
+
*/
|
|
604
|
+
async _removeRecord (model, id, options = {}) {
|
|
605
|
+
if (!options._data) {
|
|
606
|
+
const resp = await this.getRecord(model, id, { noMagic: true })
|
|
607
|
+
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
608
|
+
options._data = resp.data
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
await this._attachHook('beforeRemoveRecord', model, id, options)
|
|
612
|
+
const result = await this.removeRecord(model, id, options)
|
|
613
|
+
await this._attachHook('afterRemoveRecord', model, id, result, options)
|
|
614
|
+
|
|
615
|
+
if (options.noResult) return
|
|
616
|
+
result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
617
|
+
this._injectMeta(result, options)
|
|
618
|
+
return result
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Wrapper for the {@link DoboAdapter#clearRecord} method, called internally by `model` to make sure
|
|
623
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
624
|
+
* @internal
|
|
625
|
+
* @async
|
|
626
|
+
* @method
|
|
627
|
+
* @param {DoboModel} model
|
|
628
|
+
* @param {object} options
|
|
629
|
+
* @returns {Promise<object>}
|
|
630
|
+
*/
|
|
631
|
+
async _clearRecord (model, options = {}) {
|
|
632
|
+
await this._attachHook('beforeClearRecord', model, options)
|
|
633
|
+
const result = await this.clearRecord(model, options)
|
|
634
|
+
await this._attachHook('afterClearRecord', model, result, options)
|
|
635
|
+
|
|
636
|
+
this._injectMeta(result, options)
|
|
637
|
+
return result
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Wrapper for the {@link DoboAdapter#findRecord} method, called internally by `model` to make sure
|
|
642
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
643
|
+
* @internal
|
|
644
|
+
* @async
|
|
645
|
+
* @method
|
|
646
|
+
* @param {DoboModel} model
|
|
647
|
+
* @param {object} filter
|
|
648
|
+
* @param {object} options
|
|
649
|
+
* @returns {Promise<object>}
|
|
650
|
+
*/
|
|
651
|
+
async _findRecord (model, filter = {}, options = {}) {
|
|
652
|
+
let result
|
|
653
|
+
try {
|
|
654
|
+
await this._attachHook('beforeFindRecord', model, filter, options)
|
|
655
|
+
result = await this.findRecord(model, filter, options)
|
|
656
|
+
await this._attachHook('afterFindRecord', model, filter, result, options)
|
|
657
|
+
} catch (err) {
|
|
658
|
+
if (!['_emptyColumnQuery', '_abortAction'].includes(err.message)) throw err
|
|
659
|
+
result = {
|
|
660
|
+
data: [],
|
|
661
|
+
count: 0
|
|
662
|
+
// warnings: [] // TODO: should generate warnings?
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
for (const idx in result.data) {
|
|
667
|
+
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
668
|
+
}
|
|
669
|
+
this._injectMeta(result, options)
|
|
670
|
+
return result
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Finds all records for the given model based on the provided filter.
|
|
675
|
+
* This method will only run if child adapter does not implement {@link DoboAdapter#findAllRecord}.
|
|
676
|
+
*
|
|
677
|
+
* @internal
|
|
678
|
+
* @async
|
|
679
|
+
* @method
|
|
680
|
+
* @param {DoboModel} model - The model instance for which the records are being found
|
|
681
|
+
* @param {object} filter - The filter criteria for finding records
|
|
682
|
+
* @param {object} options - Additional options that may affect record finding
|
|
683
|
+
* @returns {Promise<object>} - The result of the record finding
|
|
684
|
+
*/
|
|
685
|
+
async _findAllRecord (model, filter = {}, options = {}) {
|
|
686
|
+
let result
|
|
687
|
+
try {
|
|
688
|
+
await this._attachHook('beforeFindAllRecord', model, filter, options)
|
|
689
|
+
result = await this.findAllRecord(model, filter, options)
|
|
690
|
+
await this._attachHook('afterFindAllRecord', model, filter, result, options)
|
|
691
|
+
} catch (err) {
|
|
692
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
693
|
+
result = {
|
|
694
|
+
data: [],
|
|
695
|
+
count: 0
|
|
696
|
+
// warnings: [] // TODO: should generate warnings?
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
for (const idx in result.data) {
|
|
701
|
+
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
702
|
+
}
|
|
703
|
+
this._injectMeta(result, options)
|
|
704
|
+
return result
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Wrapper for the {@link DoboAdapter#countRecord} method, called internally by `model` to make sure
|
|
709
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
710
|
+
* @internal
|
|
711
|
+
* @async
|
|
712
|
+
* @method
|
|
713
|
+
* @param {DoboModel} model
|
|
714
|
+
* @param {object} filter
|
|
715
|
+
* @param {object} options
|
|
716
|
+
* @returns {Promise<object>}
|
|
717
|
+
*/
|
|
718
|
+
async _countRecord (model, filter = {}, options = {}) {
|
|
719
|
+
let result
|
|
720
|
+
try {
|
|
721
|
+
await this._attachHook('beforeCountRecord', model, filter, options)
|
|
722
|
+
result = await this.countRecord(model, filter, options)
|
|
723
|
+
await this._attachHook('afterCountRecord', model, filter, result, options)
|
|
724
|
+
} catch (err) {
|
|
725
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
726
|
+
result = { data: 0 }
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return result
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Wrapper for the {@link DoboAdapter#createAggregate} method, called internally by `model` to make sure
|
|
734
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
735
|
+
* @internal
|
|
736
|
+
* @async
|
|
737
|
+
* @method
|
|
738
|
+
* @param {DoboModel} model
|
|
739
|
+
* @param {object} filter
|
|
740
|
+
* @param {object} params
|
|
741
|
+
* @param {object} options
|
|
742
|
+
* @returns {Promise<object>}
|
|
743
|
+
*/
|
|
744
|
+
async _createAggregate (model, filter = {}, params = {}, options = {}) {
|
|
745
|
+
const fieldPropTypes = ['integer', 'smallint', 'float', 'double']
|
|
746
|
+
const groupPropTypes = ['string', ...fieldPropTypes]
|
|
747
|
+
this.app.dobo.checkAggregateParams(params)
|
|
748
|
+
const { group, field } = params
|
|
749
|
+
|
|
750
|
+
let prop = model.properties.find(p => p.name === group)
|
|
751
|
+
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), group)
|
|
752
|
+
if (!groupPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', group, groupPropTypes.join(', '))
|
|
753
|
+
|
|
754
|
+
prop = model.properties.find(p => p.name === field)
|
|
755
|
+
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
|
|
756
|
+
// if (!fieldPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', field, fieldPropTypes.join(', '))
|
|
757
|
+
|
|
758
|
+
let result
|
|
759
|
+
try {
|
|
760
|
+
await this._attachHook('beforeCreateAggregate', model, filter, params, options)
|
|
761
|
+
result = await this.createAggregate(model, filter, params, options)
|
|
762
|
+
await this._attachHook('afterCreateAggregate', model, filter, params, result, options)
|
|
763
|
+
} catch (err) {
|
|
764
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
765
|
+
result = { data: [] }
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
for (const idx in result.data) {
|
|
769
|
+
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
770
|
+
}
|
|
771
|
+
this._injectMeta(result, options)
|
|
772
|
+
return result
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Wrapper for the {@link DoboAdapter#createHistogram} method, called internally by `model` to make sure
|
|
777
|
+
* all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
|
|
778
|
+
* @internal
|
|
779
|
+
* @async
|
|
780
|
+
* @method
|
|
781
|
+
* @param {DoboModel} model
|
|
782
|
+
* @param {object} filter
|
|
783
|
+
* @param {object} params
|
|
784
|
+
* @param {object} options
|
|
785
|
+
* @returns {Promise<object>}
|
|
786
|
+
*/
|
|
787
|
+
async _createHistogram (model, filter = {}, params, options = {}) {
|
|
788
|
+
// const fieldPropTypes = ['integer', 'smallint', 'float', 'double']
|
|
789
|
+
const groupPropTypes = ['datetime', 'date']
|
|
790
|
+
this.app.dobo.checkHistogramParams(params)
|
|
791
|
+
const { group, field } = params
|
|
792
|
+
|
|
793
|
+
let prop = model.properties.find(p => p.name === group)
|
|
794
|
+
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), group)
|
|
795
|
+
if (!groupPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', group, groupPropTypes.join(', '))
|
|
796
|
+
|
|
797
|
+
prop = model.properties.find(p => p.name === field)
|
|
798
|
+
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
|
|
799
|
+
|
|
800
|
+
let result
|
|
801
|
+
try {
|
|
802
|
+
await this._attachHook('beforeCreateHistogram', model, filter, params, options)
|
|
803
|
+
result = await this.createHistogram(model, filter, params, options)
|
|
804
|
+
await this._attachHook('afterCreateHistogram', model, filter, params, result, options)
|
|
805
|
+
} catch (err) {
|
|
806
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
807
|
+
result = { data: [] }
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
for (const idx in result.data) {
|
|
811
|
+
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
812
|
+
}
|
|
813
|
+
this._injectMeta(result, options)
|
|
814
|
+
return result
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Public calls that need to be implemented by child adapters
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Connects to the database using the provided connection parameters.
|
|
821
|
+
* @param {*} connection - The connection parameters for the database
|
|
822
|
+
* @param {*} noRebuild - Flag indicating whether to skip rebuilding the database schema
|
|
823
|
+
* @returns {Promise<void>} - Resolves when the connection is established
|
|
824
|
+
*/
|
|
825
|
+
async connect (connection, noRebuild) {
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Checks if the model exists in the database.
|
|
830
|
+
*
|
|
831
|
+
* Must be implemented by child classes to provide specific database functionality for model existence checking,
|
|
832
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
833
|
+
*
|
|
834
|
+
* @param {DoboModel} model - The model instance to check for existence
|
|
835
|
+
* @param {object} options - Additional options that may affect the existence check
|
|
836
|
+
*/
|
|
837
|
+
async modelExists (model, options = {}) {
|
|
838
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'modelExists', this.name)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Builds the model in the database.
|
|
843
|
+
*
|
|
844
|
+
* Must be implemented by child classes to provide specific database functionality for model building,
|
|
845
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
846
|
+
*
|
|
847
|
+
* @param {DoboModel} model - The model instance for which the record is being built
|
|
848
|
+
* @param {object} options - Additional options that may affect model building
|
|
849
|
+
*/
|
|
850
|
+
async buildModel (model, options = {}) {
|
|
851
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'buildModel', this.name)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Drops the model from the database.
|
|
856
|
+
*
|
|
857
|
+
* Must be implemented by child classes to provide specific database functionality for dropping a model,
|
|
858
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
859
|
+
*
|
|
860
|
+
* @param {DoboModel} model - The model instance for which the record is being dropped
|
|
861
|
+
* @param {object} options - Additional options that may affect record dropping
|
|
862
|
+
*/
|
|
863
|
+
async dropModel (model, options = {}) {
|
|
864
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'dropModel', this.name)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Creates a new record for the given model.
|
|
869
|
+
*
|
|
870
|
+
* Must be implemented by child classes to provide specific database functionality for record creation,
|
|
871
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
872
|
+
*
|
|
873
|
+
* @param {DoboModel} model - The model instance for which the record is being created
|
|
874
|
+
* @param {object} input - The input data for the new record
|
|
875
|
+
* @param {object} options - Additional options that may affect record creation
|
|
876
|
+
* @returns {Promise<object>} - The result of the record creation
|
|
877
|
+
*/
|
|
878
|
+
async createRecord (model, body = {}, options = {}) {
|
|
879
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createRecord', this.name)
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Retrieves a record for the given model by its ID.
|
|
884
|
+
*
|
|
885
|
+
* Must be implemented by child classes to provide specific database functionality for record retrieval,
|
|
886
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
887
|
+
*
|
|
888
|
+
* @param {DoboModel} model - The model instance for which the record is being retrieved
|
|
889
|
+
* @param {string|number} id - The ID of the record to retrieve
|
|
890
|
+
* @param {object} options - Additional options that may affect record retrieval
|
|
891
|
+
* @returns {Promise<object>} - The result of the record retrieval
|
|
892
|
+
*/
|
|
893
|
+
async getRecord (model, id, options = {}) {
|
|
894
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'getRecord', this.name)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Updates a record for the given model by its ID.
|
|
899
|
+
*
|
|
900
|
+
* Must be implemented by child classes to provide specific database functionality for record updating,
|
|
901
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
902
|
+
*
|
|
903
|
+
* @param {DoboModel} model - The model instance for which the record is being updated
|
|
904
|
+
* @param {string|number} id - The ID of the record to update
|
|
905
|
+
* @param {object} input - The input data for the update
|
|
906
|
+
* @param {object} options - Additional options that may affect record updating
|
|
907
|
+
* @returns {Promise<object>} - The result of the record update
|
|
908
|
+
*/
|
|
909
|
+
async updateRecord (model, id, body = {}, options = {}) {
|
|
910
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'updateRecord', this.name)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Removes a record for the given model by its ID.
|
|
915
|
+
*
|
|
916
|
+
* Must be implemented by child classes to provide specific database functionality for record removal,
|
|
917
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
918
|
+
*
|
|
919
|
+
* @param {DoboModel} model - The model instance for which the record is being removed
|
|
920
|
+
* @param {string|number} id - The ID of the record to remove
|
|
921
|
+
* @param {object} options - Additional options that may affect record removal
|
|
922
|
+
* @returns {Promise<object>} - The result of the record removal
|
|
923
|
+
*/
|
|
924
|
+
async removeRecord (model, id, options = {}) {
|
|
925
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'removeRecord', this.name)
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Clears all records for the given model.
|
|
930
|
+
*
|
|
931
|
+
* Must be implemented by child classes to provide specific database functionality for record clearing,
|
|
932
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
933
|
+
*
|
|
934
|
+
* @param {DoboModel} model - The model instance for which the records are being cleared
|
|
935
|
+
* @param {object} options - Additional options that may affect record clearing
|
|
936
|
+
* @returns {Promise<object>} - The result of the record clearing
|
|
937
|
+
*/
|
|
938
|
+
async clearRecord (model, options = {}) {
|
|
939
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'clearRecord', this.name)
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Finds records for the given model based on the provided filter.
|
|
944
|
+
*
|
|
945
|
+
* Must be implemented by child classes to provide specific database functionality for record finding,
|
|
946
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
947
|
+
*
|
|
948
|
+
* @param {DoboModel} model - The model instance for which the records are being found
|
|
949
|
+
* @param {object} filter - The filter criteria for finding records
|
|
950
|
+
* @param {object} options - Additional options that may affect record finding
|
|
951
|
+
* @returns {Promise<object>} - The result of the record finding
|
|
952
|
+
*/
|
|
953
|
+
async findRecord (model, filter = {}, options = {}) {
|
|
954
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'findRecord', this.name)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Bulk creates records for the given model.
|
|
959
|
+
*
|
|
960
|
+
* Must be implemented by child classes to provide specific database functionality for bulk record creation,
|
|
961
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
962
|
+
*
|
|
963
|
+
* @param {DoboModel} model - The model instance for which the records are being created
|
|
964
|
+
* @param {Array<object>} bodies - The array of input data for the new records
|
|
965
|
+
* @param {object} options - Additional options that may affect record creation
|
|
966
|
+
* @returns {Promise<void>} - Resolves when the records have been created
|
|
967
|
+
*/
|
|
968
|
+
async bulkCreateRecord (model, bodies = [], options = {}) {
|
|
969
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'bulkCreateRecord', this.name)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Counts the records for the given model based on the provided filter.
|
|
974
|
+
*
|
|
975
|
+
* Must be implemented by child classes to provide specific database functionality for record counting,
|
|
976
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
977
|
+
*
|
|
978
|
+
* @param {DoboModel} model - The model instance for which the records are being counted
|
|
979
|
+
* @param {object} filter - The filter criteria for counting records
|
|
980
|
+
* @param {object} options - Additional options that may affect record counting
|
|
981
|
+
* @returns {Promise<object>} - The result of the record counting
|
|
982
|
+
*/
|
|
983
|
+
async countRecord (model, filter = {}, options = {}) {
|
|
984
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'countRecord', this.name)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Creates an aggregate for the given model based on the provided filter and parameters.
|
|
989
|
+
*
|
|
990
|
+
* Must be implemented by child classes to provide specific database functionality for aggregate creation,
|
|
991
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
992
|
+
*
|
|
993
|
+
* @param {DoboModel} model - The model instance for which the aggregate is being created
|
|
994
|
+
* @param {object} filter - The filter criteria for creating the aggregate
|
|
995
|
+
* @param {object} params - The parameters for the aggregate creation
|
|
996
|
+
* @param {object} options - Additional options that may affect aggregate creation
|
|
997
|
+
* @returns {Promise<object>} - The result of the aggregate creation
|
|
998
|
+
*/
|
|
999
|
+
async createAggregate (model, filter = {}, params = {}, options = {}) {
|
|
1000
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createAggregate', this.name)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Creates a histogram for the given model based on the provided filter and parameters.
|
|
1005
|
+
*
|
|
1006
|
+
* Must be implemented by child classes to provide specific database functionality for histogram creation,
|
|
1007
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
1008
|
+
*
|
|
1009
|
+
* @param {DoboModel} model - The model instance for which the histogram is being created
|
|
1010
|
+
* @param {object} filter - The filter criteria for creating the histogram
|
|
1011
|
+
* @param {object} params - The parameters for the histogram creation
|
|
1012
|
+
* @param {object} options - Additional options that may affect histogram creation
|
|
1013
|
+
* @returns {Promise<object>} - The result of the histogram creation
|
|
1014
|
+
*/
|
|
1015
|
+
async createHistogram (model, filter = {}, params = {}, options = {}) {
|
|
1016
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createHistogram', this.name)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Executes a transaction for the given model.
|
|
1021
|
+
*
|
|
1022
|
+
* Must be implemented by child classes to provide specific database functionality for transactions,
|
|
1023
|
+
* or throw an error if the operation is not supported by the adapter.
|
|
1024
|
+
*
|
|
1025
|
+
* @param {DoboModel} model - The model instance for which the transaction is being executed
|
|
1026
|
+
* @param {Function} handler - The transaction handler function
|
|
1027
|
+
* @param {...any} args - Additional arguments for the transaction handler
|
|
1028
|
+
*/
|
|
1029
|
+
async transaction (model, handler, ...args) {
|
|
1030
|
+
throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'transaction', this.name)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Disposes of the adapter, performing any necessary cleanup operations.
|
|
1035
|
+
*
|
|
1036
|
+
* @returns {Promise<void>} - Resolves when the adapter has been disposed
|
|
1037
|
+
*/
|
|
1038
|
+
async dispose () {
|
|
1039
|
+
await super.dispose()
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
class DoboNullAdapter extends DoboAdapter {
|
|
1044
|
+
constructor (plugin, name = 'null', options = {}) {
|
|
1045
|
+
super(plugin, name, options)
|
|
1046
|
+
this.memory = true
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
this.app.baseClass.DoboAdapter = DoboAdapter
|
|
1051
|
+
this.app.baseClass.DoboNullAdapter = DoboNullAdapter
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export default adapterFactory
|