dobo 2.3.1 → 2.5.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/index.js CHANGED
@@ -480,6 +480,15 @@ async function factory (pkgName) {
480
480
  if (isEmpty(type)) throw this.error('histogramTypeMissing')
481
481
  if (!this.constructor.histogramTypes.includes(type)) throw this.error('unsupportedHistogramType%s', type)
482
482
  }
483
+
484
+ execModelHook = async (model, hookName, ...args) => {
485
+ const { orderBy } = this.app.lib._
486
+ const hooks = orderBy(model.hooks.filter(hook => hook.name === hookName), ['level'])
487
+ for (const hook of hooks) {
488
+ if (hook.noWait) hook.handler.call(model, ...args)
489
+ else await hook.handler.call(model, ...args)
490
+ }
491
+ }
483
492
  }
484
493
 
485
494
  return Dobo
@@ -1,5 +1,6 @@
1
1
  import path from 'path'
2
2
  import modelFactory from './factory/model.js'
3
+ import actionFactory from './factory/action.js'
3
4
 
4
5
  /**
5
6
  * Sanitize one single property of a model
@@ -285,6 +286,7 @@ async function createSchema (item) {
285
286
  async function collectModels () {
286
287
  const { eachPlugins } = this.app.bajo
287
288
  const { orderBy, has } = this.app.lib._
289
+ await actionFactory.call(this)
288
290
  const DoboModel = await modelFactory.call(this)
289
291
 
290
292
  this.log.trace('collecting%s', this.t('model'))
@@ -37,6 +37,7 @@ async function driverFactory () {
37
37
  uniqueIndex: false,
38
38
  nullableField: true
39
39
  }
40
+ this.maxChunkSize = 500
40
41
  this.memory = false
41
42
  this.options = options
42
43
  }
@@ -154,7 +155,7 @@ async function driverFactory () {
154
155
  return await this.dropModel(model, options)
155
156
  }
156
157
 
157
- async _createRecord (model, body = {}, options = {}) {
158
+ async _prepBodyForCreate (model, body = {}, options = {}) {
158
159
  const { isSet, generateId } = this.app.lib.aneka
159
160
  const { isFunction } = this.app.lib._
160
161
  for (const prop of model.properties) {
@@ -185,6 +186,11 @@ async function driverFactory () {
185
186
  }
186
187
  }
187
188
  }
189
+ }
190
+
191
+ async _prepIdForCreate (model, body = {}, options = {}) {
192
+ const { isSet, generateId } = this.app.lib.aneka
193
+ const { isFunction } = this.app.lib._
188
194
  const prop = model.properties.find(p => p.name === 'id')
189
195
  if (!isSet(body.id) && prop.type === 'string') {
190
196
  if (this.idGenerator) {
@@ -196,17 +202,42 @@ async function driverFactory () {
196
202
  if (!body.id) body.id = ulid()
197
203
  body.id = body.id.slice(0, prop.maxLength)
198
204
  }
199
- if (!this.uniqueIndexSupport) await this._checkUnique(model, body, options)
200
- if (isSet(body.id)) {
205
+ }
206
+
207
+ async _createRecord (model, body = {}, options = {}) {
208
+ const { isSet } = this.app.lib.aneka
209
+ await this._prepBodyForCreate(model, body, options)
210
+ await this._prepIdForCreate(model, body, options)
211
+ if (!options.noUniqueCheck) {
212
+ if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
213
+ }
214
+ if (!options.noIdCheck && isSet(body.id)) {
201
215
  const resp = await this.getRecord(model, body.id, { noHook: true })
202
216
  if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
203
217
  }
204
- const result = await this.createRecord(model, this.sanitizeBody(model, body), options)
218
+ const input = this.sanitizeBody(model, body)
219
+ const result = await this.createRecord(model, input, options)
205
220
  if (options.noResult) return
206
221
  result.data = this.sanitizeRecord(model, result.data)
207
222
  return result
208
223
  }
209
224
 
225
+ async _bulkCreateRecords (model, bodies = [], options = {}) {
226
+ const { chunk } = this.app.lib._
227
+ let { chunkSize = this.maxChunkSize } = options
228
+ if (chunkSize > this.maxChunkSize) chunkSize = this.maxChunkSize
229
+ for (const idx in bodies) {
230
+ const body = bodies[idx]
231
+ await this._prepBodyForCreate(model, body, options)
232
+ await this._prepIdForCreate(model, body, options)
233
+ bodies[idx] = this.sanitizeBody(model, body)
234
+ }
235
+ const items = chunk(bodies, chunkSize)
236
+ for (const item of items) {
237
+ await this.bulkCreateRecords(model, item, options)
238
+ }
239
+ }
240
+
210
241
  async _getRecord (model, id, options = {}) {
211
242
  const result = await this.getRecord(model, id, options)
212
243
  if (isEmpty(result.data) && options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
@@ -216,9 +247,11 @@ async function driverFactory () {
216
247
 
217
248
  async _updateRecord (model, id, body = {}, options = {}) {
218
249
  if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
219
- const resp = await this.getRecord(model, id, { noHook: true })
220
- if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
221
- options._data = resp.data
250
+ if (!options._data) {
251
+ const resp = await this.getRecord(model, id, { noHook: true })
252
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
253
+ options._data = resp.data
254
+ }
222
255
  const input = this.sanitizeBody(model, body, true)
223
256
  delete input.id
224
257
  const result = await this.updateRecord(model, id, input, options)
@@ -231,11 +264,14 @@ async function driverFactory () {
231
264
  async _upsertRecord (model, body = {}, options = {}) {
232
265
  if (!this.uniqueIndexSupport) await this._checkUnique(model, body, options)
233
266
  if (isSet(body.id)) {
234
- const resp = await this.getRecord(model, body.id, { noHook: true })
235
- if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
236
- options._data = resp.data
267
+ if (!options._data) {
268
+ const resp = await this.getRecord(model, body.id, { noHook: true })
269
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
270
+ options._data = resp.data
271
+ }
237
272
  }
238
- const result = await this.upsertRecord(model, this.sanitizeBody(model, body), options)
273
+ const input = this.sanitizeBody(model, body)
274
+ const result = await this.upsertRecord(model, input, options)
239
275
  if (options.noResult) return
240
276
  if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
241
277
  result.data = this.sanitizeRecord(model, result.data)
@@ -243,9 +279,11 @@ async function driverFactory () {
243
279
  }
244
280
 
245
281
  async _removeRecord (model, id, options = {}) {
246
- const resp = await this.getRecord(model, id, { noHook: true })
247
- if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
248
- options._data = resp.data
282
+ if (!options._data) {
283
+ const resp = await this.getRecord(model, id, { noHook: true })
284
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
285
+ options._data = resp.data
286
+ }
249
287
  const result = await this.removeRecord(model, id, options)
250
288
  if (options.noResult) return
251
289
  result.oldData = this.sanitizeRecord(model, result.oldData)
@@ -359,6 +397,10 @@ async function driverFactory () {
359
397
  throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'findRecord')
360
398
  }
361
399
 
400
+ async bulkCreateRecords (model, bodies = [], options = {}) {
401
+ throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'bulkCreateRecords')
402
+ }
403
+
362
404
  async countRecord (model, filter = {}, options = {}) {
363
405
  throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'countRecord')
364
406
  }
@@ -13,17 +13,10 @@ export async function execHook (name, ...args) {
13
13
  }
14
14
 
15
15
  export async function execModelHook (name, ...args) {
16
- const { last, orderBy } = this.app.lib._
16
+ const { last } = this.app.lib._
17
+ const { execModelHook } = this.app.dobo
17
18
  const { noModelHook } = last(args)
18
- const results = []
19
- if (!noModelHook) {
20
- const hooks = orderBy(this.hooks.filter(hook => hook.name === name), ['level'])
21
- for (const hook of hooks) {
22
- if (hook.noWait) hook.handler.call(this, ...args)
23
- else await hook.handler.call(this, ...args)
24
- }
25
- }
26
- return results
19
+ if (!noModelHook) await execModelHook(this, name, ...args)
27
20
  }
28
21
 
29
22
  export async function execDynHook (name, ...args) {
@@ -131,7 +124,7 @@ export async function copyAttachment (id, options = {}) {
131
124
  if (parts.length === 0) continue
132
125
  fieldName = setField ?? fieldName
133
126
  const file = setFile ?? parts.join('@')
134
- const opts = { source: f, fieldName, file, mimeType, stats, req, silent: true }
127
+ const opts = { source: f, fieldName, file, mimeType, stats, req }
135
128
  const rec = await this.createAttachment(id, opts)
136
129
  if (!rec) continue
137
130
  delete rec.dir
@@ -210,7 +203,6 @@ export async function getMultiRefs (records = [], options = {}) {
210
203
  query[ref.propName] = { $in: matches }
211
204
  const rFilter = { query, limit: matches.length }
212
205
  const rOptions = { dataOnly: true, refs: [] }
213
- this.app.dump(this.name, rModel.name, rFilter, records)
214
206
  const results = await rModel.findRecord(rFilter, rOptions)
215
207
  const fields = [...ref.fields]
216
208
  if (!fields.includes(prop.name)) fields.push(prop.name)
@@ -0,0 +1,36 @@
1
+ import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook } from './_util.js'
2
+
3
+ export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
4
+ const action = 'bulkCreateRecords'
5
+
6
+ async function bulkCreateRecords (...args) {
7
+ if (args.length === 0) return this.action(action, ...args)
8
+ const [bodies = [], opts = {}] = args
9
+ const { cloneDeep, get } = this.app.lib._
10
+ const { options } = await getFilterAndOptions.call(this, null, opts, action)
11
+ const { truncateString, noBodySanitizer, noValidation } = options
12
+ const extFields = get(options, 'validation.extFields', [])
13
+
14
+ const inputs = cloneDeep(bodies)
15
+ if (!noBodySanitizer) {
16
+ for (const idx in inputs) {
17
+ inputs[idx] = await this.sanitizeBody({ body: inputs[idx], extFields, strict: true, truncateString, onlyTypes })
18
+ }
19
+ }
20
+ await execHook.call(this, 'beforeBulkCreateRecords', inputs, options)
21
+ await execModelHook.call(this, 'beforeBulkCreateRecords', inputs, options)
22
+ await execDynHook.call(this, 'beforeBulkCreateRecords', inputs, options)
23
+ if (!noValidation) {
24
+ for (const input of inputs) {
25
+ await execValidation.call(this, input, options)
26
+ }
27
+ }
28
+ // TODO: bulk don't return anything currently, it should return at least a stat
29
+ await this.driver._bulkCreateRecords(this, inputs, options)
30
+ await execDynHook.call(this, 'afterBulkCreateRecords', inputs, options)
31
+ await execModelHook.call(this, 'afterBulkCreateRecords', inputs, options)
32
+ await execHook.call(this, 'afterBulkCreateRecords', inputs, options)
33
+ return []
34
+ }
35
+
36
+ export default bulkCreateRecords
@@ -60,7 +60,8 @@ async function loop (params, opts, dataOnly) {
60
60
  }
61
61
 
62
62
  async function findAllRecord (...args) {
63
- if (args.length === 0) return this.action(action, ...args)
63
+ // can't use action here, because people tends to use is without arguments
64
+ // if (args.length === 0) return this.action(action, ...args)
64
65
  const [params = {}, opts = {}] = args
65
66
  const { dataOnly = true } = opts
66
67
  if (this.driver.findAllRecord) {
@@ -23,6 +23,7 @@ import sanitizeBody from './model/sanitize-body.js'
23
23
  import sanitizeRecord from './model/sanitize-record.js'
24
24
  import sanitizeId from './model/sanitize-id.js'
25
25
  import upsertRecord from './model/upsert-record.js'
26
+ import bulkCreateRecords from './model/bulk-create-records.js'
26
27
  import listAttachment from './model/list-attachment.js'
27
28
  import validate from './model/validate.js'
28
29
 
@@ -108,6 +109,8 @@ async function modelFactory () {
108
109
  findOneRecord = findOneRecord
109
110
  findAllRecord = findAllRecord
110
111
 
112
+ bulkCreateRecords = bulkCreateRecords
113
+
111
114
  createAggregate = createAggregate
112
115
  createHistogram = createHistogram
113
116
  countRecord = countRecord
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-01-30
4
+
5
+ - [2.5.0] Add feature to push custom ```options._data```. If provided, this will be used instead of auto generated one.
6
+ - [2.5.0] Remove ```silent``` in ```options``` object
7
+
8
+ ## 2026-01-29
9
+
10
+ - [2.4.0] Add ```bulkCreateRecords()``` on model & driver
11
+ - [2.4.0] Add ```execModelHook()```
12
+ - [2.4.0] Bug fix on models collection
13
+ - [2.4.0] Add ```DoboAction``` to the ```app.baseClass```
14
+ - [2.4.0] ```findAllRecord()``` can't be called as action
15
+
3
16
  ## 2026-01-26
4
17
 
5
18
  - [2.3.1] Bug fix if reference model isn't loaded only yield warning