dobo 2.3.0 → 2.4.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
@@ -126,7 +127,7 @@ async function findAllIndexes (model, inputs = []) {
126
127
  * @param {Object} model - Model
127
128
  * @param {Array} [models] - All model match agaist. Defaults to ```dobo.models```
128
129
  */
129
- export async function sanitizeRef (model, models, fatal) {
130
+ export async function sanitizeRef (model, models) {
130
131
  const { find, isString, pullAt } = this.app.lib._
131
132
  if (!models) models = this.models
132
133
  for (const prop of model.properties) {
@@ -139,13 +140,15 @@ export async function sanitizeRef (model, models, fatal) {
139
140
  ref.type = ref.type ?? '1:1'
140
141
  const rModel = find(models, { name: ref.model })
141
142
  if (!rModel) {
142
- if (fatal) this.fatal('unknownModelForRef%s%s%s', ref.model, model.name, prop.name)
143
- else ignored.push(ref.model)
143
+ ignored.push(key)
144
+ this.log.warn('notFound%s%s', this.t('model'), ref.model)
145
+ continue
144
146
  }
145
147
  const rProp = find(rModel.properties, { name: ref.propName })
146
148
  if (!rProp) {
147
- if (fatal) this.fatal('unknownPropForRef%s%s%s%s', ref.model, ref.propName, model.name, prop.name)
148
- else ignored.push(ref.model)
149
+ ignored.push(key)
150
+ this.log.warn('notFound%s%s', this.t('property'), `${ref.propName}@${ref.model}`)
151
+ continue
149
152
  }
150
153
  ref.fields = ref.fields ?? '*'
151
154
  if (['*', 'all'].includes(ref.fields)) ref.fields = rModel.properties.map(p => p.name)
@@ -283,6 +286,7 @@ async function createSchema (item) {
283
286
  async function collectModels () {
284
287
  const { eachPlugins } = this.app.bajo
285
288
  const { orderBy, has } = this.app.lib._
289
+ await actionFactory.call(this)
286
290
  const DoboModel = await modelFactory.call(this)
287
291
 
288
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)
@@ -235,7 +266,8 @@ async function driverFactory () {
235
266
  if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
236
267
  options._data = resp.data
237
268
  }
238
- const result = await this.upsertRecord(model, this.sanitizeBody(model, body), options)
269
+ const input = this.sanitizeBody(model, body)
270
+ const result = await this.upsertRecord(model, input, options)
239
271
  if (options.noResult) return
240
272
  if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
241
273
  result.data = this.sanitizeRecord(model, result.data)
@@ -359,6 +391,10 @@ async function driverFactory () {
359
391
  throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'findRecord')
360
392
  }
361
393
 
394
+ async bulkCreateRecords (model, bodies = [], options = {}) {
395
+ throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'bulkCreateRecords')
396
+ }
397
+
362
398
  async countRecord (model, filter = {}, options = {}) {
363
399
  throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'countRecord')
364
400
  }
@@ -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) {
@@ -189,7 +182,7 @@ export async function getSingleRef (record = {}, options = {}) {
189
182
 
190
183
  export async function getMultiRefs (records = [], options = {}) {
191
184
  const { isSet } = this.app.lib.aneka
192
- const { uniq, map, get } = this.app.lib._
185
+ const { uniq, without, get } = this.app.lib._
193
186
  const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
194
187
  options.refs = options.refs ?? []
195
188
  if (props.length > 0) {
@@ -201,11 +194,11 @@ export async function getMultiRefs (records = [], options = {}) {
201
194
  if (ref.type !== '1:1') continue
202
195
  if (get(records, `0._ref.${key}`)) continue
203
196
  const rModel = this.app.dobo.getModel(ref.model)
204
- const matches = uniq(map(records, r => {
205
- let v = r[prop.name]
206
- if (ref.propName === 'id') v = this.sanitizeId(v)
207
- return v
208
- }))
197
+ let matches = []
198
+ for (const r of records) {
199
+ matches.push(rModel.sanitizeId(r[prop.name]))
200
+ }
201
+ matches = uniq(without(matches, undefined, null, NaN))
209
202
  const query = {}
210
203
  query[ref.propName] = { $in: matches }
211
204
  const rFilter = { query, limit: matches.length }
@@ -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.0",
3
+ "version": "2.4.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-29
4
+
5
+ - [2.4.0] Add ```bulkCreateRecords()``` on model & driver
6
+ - [2.4.0] Add ```execModelHook()```
7
+ - [2.4.0] Bug fix on models collection
8
+ - [2.4.0] Add ```DoboAction``` to the ```app.baseClass```
9
+ - [2.4.0] ```findAllRecord()``` can't be called as action
10
+
11
+ ## 2026-01-26
12
+
13
+ - [2.3.1] Bug fix if reference model isn't loaded only yield warning
14
+ - [2.3.1] Bug fix on fetching multi references
15
+
3
16
  ## 2026-01-24
4
17
 
5
18
  - [2.3.0] Add ```dynHooks``` to model's options