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.
Files changed (123) hide show
  1. package/.jsdoc.conf.json +1 -1
  2. package/docs/Dobo.html +1 -24
  3. package/docs/DoboAction.html +13 -0
  4. package/docs/DoboAdapter.html +3 -0
  5. package/docs/DoboConnection.html +3 -0
  6. package/docs/DoboFeature.html +3 -0
  7. package/docs/DoboModel.html +51 -0
  8. package/docs/data/search.json +1 -1
  9. package/docs/extend_bajo_hook-docs.js.html +241 -0
  10. package/docs/external-Tools.html +3 -0
  11. package/docs/global.html +1 -5
  12. package/docs/index.html +1 -1
  13. package/docs/index.js.html +434 -380
  14. package/docs/lib_factory_action.js.html +290 -0
  15. package/docs/lib_factory_adapter.js.html +1057 -0
  16. package/docs/lib_factory_connection.js.html +112 -0
  17. package/docs/lib_factory_feature.js.html +55 -0
  18. package/docs/lib_factory_model.js.html +503 -0
  19. package/docs/lib_factory_model_build.js.html +28 -0
  20. package/docs/lib_factory_model_clear-record.js.html +33 -0
  21. package/docs/lib_factory_model_count-record.js.html +45 -0
  22. package/docs/lib_factory_model_create-record.js.html +52 -0
  23. package/docs/lib_factory_model_drop.js.html +29 -0
  24. package/docs/lib_factory_model_exists.js.html +29 -0
  25. package/docs/lib_factory_model_find-all-record.js.html +115 -0
  26. package/docs/lib_factory_model_find-one-record.js.html +41 -0
  27. package/docs/lib_factory_model_find-record.js.html +85 -0
  28. package/docs/lib_factory_model_get-record.js.html +55 -0
  29. package/docs/lib_factory_model_helper.js.html +579 -0
  30. package/docs/lib_factory_model_remove-record.js.html +47 -0
  31. package/docs/lib_factory_model_sanitize-body.js.html +60 -0
  32. package/docs/lib_factory_model_sanitize-fixture.js.html +59 -0
  33. package/docs/lib_factory_model_sanitize-record.js.html +73 -0
  34. package/docs/lib_factory_model_update-record.js.html +61 -0
  35. package/docs/lib_factory_model_upsert-record.js.html +74 -0
  36. package/docs/{method_validate.js.html → lib_factory_model_validate.js.html} +109 -73
  37. package/docs/lib_helper.js.html +466 -0
  38. package/docs/module-Helper.html +3 -0
  39. package/docs/module-Helper_Model.html +3 -0
  40. package/docs/module-Hook.html +15 -0
  41. package/docs/scripts/core.js +477 -476
  42. package/docs/scripts/resize.js +36 -36
  43. package/docs/scripts/search.js +105 -105
  44. package/docs/scripts/third-party/fuse.js +1 -1
  45. package/docs/scripts/third-party/hljs-line-num-original.js +285 -282
  46. package/docs/scripts/third-party/hljs-line-num.js +1 -1
  47. package/docs/scripts/third-party/hljs-original.js +1202 -1195
  48. package/docs/scripts/third-party/hljs.js +1 -1
  49. package/docs/scripts/third-party/popper.js +1 -1
  50. package/docs/scripts/third-party/tippy.js +1 -1
  51. package/docs/scripts/third-party/tocbot.js +509 -508
  52. package/docs/static/home.md +3 -1
  53. package/extend/bajo/hook-docs.js +238 -0
  54. package/extend/bajo/intl/en-US.json +9 -9
  55. package/extend/bajo/intl/id.json +9 -9
  56. package/extend/bajoCli/applet/rebuild-model.js +2 -2
  57. package/extend/dobo/{driver → adapter}/memory.js +6 -6
  58. package/extend/dobo/feature/immutable.js +2 -2
  59. package/extend/dobo/feature/removed-at.js +1 -1
  60. package/index.js +33 -30
  61. package/lib/factory/action.js +145 -18
  62. package/lib/factory/adapter.js +1054 -0
  63. package/lib/factory/connection.js +46 -22
  64. package/lib/factory/feature.js +23 -3
  65. package/lib/factory/model/build.js +11 -2
  66. package/lib/factory/model/bulk-create-record.js +2 -2
  67. package/lib/factory/model/clear-record.js +13 -3
  68. package/lib/factory/model/count-record.js +13 -2
  69. package/lib/factory/model/create-aggregate.js +2 -2
  70. package/lib/factory/model/create-attachment.js +1 -1
  71. package/lib/factory/model/create-histogram.js +2 -2
  72. package/lib/factory/model/create-record.js +15 -2
  73. package/lib/factory/model/drop.js +12 -2
  74. package/lib/factory/model/exists.js +10 -6
  75. package/lib/factory/model/find-all-record.js +14 -3
  76. package/lib/factory/model/find-attachment.js +1 -1
  77. package/lib/factory/model/find-one-record.js +13 -1
  78. package/lib/factory/model/find-record.js +17 -53
  79. package/lib/factory/model/get-record.js +11 -39
  80. package/lib/factory/model/{_util.js → helper.js} +176 -16
  81. package/lib/factory/model/remove-attachment.js +1 -1
  82. package/lib/factory/model/remove-record.js +11 -28
  83. package/lib/factory/model/sanitize-body.js +14 -12
  84. package/lib/factory/model/sanitize-fixture.js +17 -4
  85. package/lib/factory/model/sanitize-record.js +9 -6
  86. package/lib/factory/model/transaction.js +2 -2
  87. package/lib/factory/model/update-record.js +12 -40
  88. package/lib/factory/model/upsert-record.js +13 -42
  89. package/lib/factory/model/validate.js +47 -14
  90. package/lib/factory/model.js +301 -23
  91. package/lib/{collect-models.js → helper.js} +463 -364
  92. package/package.json +1 -1
  93. package/wiki/CHANGES.md +17 -7
  94. package/wiki/ECOSYSTEM.md +7 -7
  95. package/wiki/GETTING-STARTED.md +3 -3
  96. package/wiki/QUERY-LANGUAGE.md +1 -1
  97. package/docs/lib_collect-connections.js.html +0 -39
  98. package/docs/lib_collect-drivers.js.html +0 -52
  99. package/docs/lib_collect-features.js.html +0 -36
  100. package/docs/lib_collect-schemas.js.html +0 -94
  101. package/docs/lib_index.js.html +0 -6
  102. package/docs/method_model_create.js.html +0 -35
  103. package/docs/method_model_drop.js.html +0 -34
  104. package/docs/method_model_exists.js.html +0 -40
  105. package/docs/method_record_count.js.html +0 -69
  106. package/docs/method_record_create.js.html +0 -114
  107. package/docs/method_record_find-all.js.html +0 -44
  108. package/docs/method_record_find-one.js.html +0 -73
  109. package/docs/method_record_find.js.html +0 -118
  110. package/docs/method_record_get.js.html +0 -92
  111. package/docs/method_record_remove.js.html +0 -75
  112. package/docs/method_record_update.js.html +0 -107
  113. package/docs/method_record_upsert.js.html +0 -54
  114. package/docs/method_sanitize_body.js.html +0 -88
  115. package/docs/method_sanitize_date.js.html +0 -30
  116. package/docs/method_sanitize_id.js.html +0 -20
  117. package/docs/module-Lib.html +0 -3
  118. package/lib/collect-connections.js +0 -56
  119. package/lib/collect-drivers.js +0 -35
  120. package/lib/collect-features.js +0 -40
  121. package/lib/factory/driver.js +0 -574
  122. package/lib/factory/model/sanitize-id.js +0 -7
  123. 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