dobo 2.22.1 → 2.24.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 (38) hide show
  1. package/extend/bajo/intl/en-US.json +1 -1
  2. package/extend/bajo/intl/id.json +1 -0
  3. package/extend/dobo/driver/memory.js +26 -25
  4. package/extend/dobo/feature/created-at.js +14 -3
  5. package/extend/dobo/feature/immutable.js +1 -2
  6. package/extend/dobo/feature/removed-at.js +7 -0
  7. package/extend/dobo/feature/unique.js +18 -6
  8. package/extend/dobo/feature/updated-at.js +15 -5
  9. package/index.js +1 -1
  10. package/lib/collect-connections.js +3 -11
  11. package/lib/collect-drivers.js +2 -2
  12. package/lib/collect-features.js +2 -2
  13. package/lib/collect-models.js +25 -16
  14. package/lib/factory/action.js +0 -1
  15. package/lib/factory/connection.js +26 -4
  16. package/lib/factory/driver.js +131 -26
  17. package/lib/factory/feature.js +0 -1
  18. package/lib/factory/model/_util.js +37 -67
  19. package/lib/factory/model/{bulk-create-records.js → bulk-create-record.js} +9 -9
  20. package/lib/factory/model/create-attachment.js +1 -1
  21. package/lib/factory/model/create-record.js +2 -2
  22. package/lib/factory/model/find-all-record.js +9 -8
  23. package/lib/factory/model/find-attachment.js +1 -1
  24. package/lib/factory/model/find-record.js +3 -2
  25. package/lib/factory/model/get-attachment.js +1 -1
  26. package/lib/factory/model/get-record.js +2 -2
  27. package/lib/factory/model/list-attachment.js +1 -1
  28. package/lib/factory/model/load-fixtures.js +24 -10
  29. package/lib/factory/model/remove-attachment.js +1 -1
  30. package/lib/factory/model/remove-record.js +2 -2
  31. package/lib/factory/model/sanitize-body.js +11 -6
  32. package/lib/factory/model/sanitize-record.js +2 -1
  33. package/lib/factory/model/transaction.js +10 -1
  34. package/lib/factory/model/update-record.js +3 -3
  35. package/lib/factory/model/upsert-record.js +2 -2
  36. package/lib/factory/model.js +18 -5
  37. package/package.json +1 -1
  38. package/wiki/CHANGES.md +33 -3
@@ -12,8 +12,9 @@ const defIdField = {
12
12
 
13
13
  async function driverFactory () {
14
14
  const { Tools } = this.app.baseClass
15
- const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction } = this.app.lib._
15
+ const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction, camelCase, last } = this.app.lib._
16
16
  const { isSet } = this.app.lib.aneka
17
+ const { runHook } = this.app.bajo
17
18
 
18
19
  /**
19
20
  * Driver class
@@ -59,11 +60,13 @@ async function driverFactory () {
59
60
  sanitizeBody (model, body = {}, partial) {
60
61
  const { keys, pick } = this.app.lib._
61
62
  const item = cloneDeep(body)
63
+ let newId = false
62
64
  if (has(item, 'id') && this.idField.name !== 'id') {
63
65
  item[this.idField.name] = item.id
64
- delete item.id
66
+ newId = true
65
67
  }
66
68
  for (const prop of model.properties) {
69
+ if (item[prop.name] === 'null') item[prop.name] = null
67
70
  if (!isSet(item[prop.name]) && !this.support.nullableField) {
68
71
  switch (prop.type) {
69
72
  case 'datetime': item[prop.name] = new Date(0); break
@@ -80,7 +83,9 @@ async function driverFactory () {
80
83
  else if (['object', 'array'].includes(prop.type)) item[prop.name] = JSON.stringify(item[prop.name])
81
84
  }
82
85
  }
83
- return partial ? pick(item, keys(body)) : item
86
+ const result = partial ? pick(item, keys(body)) : item
87
+ if (newId) delete result.id
88
+ return result
84
89
  }
85
90
 
86
91
  sanitizeRecord (model, record = {}) {
@@ -107,6 +112,7 @@ async function driverFactory () {
107
112
  const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
108
113
  item[prop.name] = dt.toDate()
109
114
  }
115
+ if (prop.type === 'boolean' && isSet(item[prop.name])) item[prop.name] = Boolean(item[prop.name])
110
116
  }
111
117
  }
112
118
  return item
@@ -120,6 +126,15 @@ async function driverFactory () {
120
126
  return uniq(items)
121
127
  }
122
128
 
129
+ async _attachHook (name, model, ...args) {
130
+ const { ns } = this.app.dobo
131
+ const options = last(args)
132
+ if (!options.noDriverHook) {
133
+ await runHook(`${ns}:${name}`, model, ...args)
134
+ await runHook(`${ns}.${camelCase(model.name)}:${name}`, model, ...args)
135
+ }
136
+ }
137
+
123
138
  /**
124
139
  * Check uniqueness of fields with unique index
125
140
  *
@@ -241,7 +256,11 @@ async function driverFactory () {
241
256
  if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
242
257
  }
243
258
  body = this.sanitizeBody(model, body)
259
+
260
+ await this._attachHook('beforeDriverCreateRecord', model, body, options)
244
261
  const result = await this.createRecord(model, body, options)
262
+ await this._attachHook('afterDriverCreateRecord', model, body, result, options)
263
+
245
264
  if (options.noResult) return
246
265
  result.data = this.sanitizeRecord(model, result.data)
247
266
  this._injectMeta(result, options)
@@ -257,14 +276,20 @@ async function driverFactory () {
257
276
  await this._prepIdForCreate(model, body, options)
258
277
  bodies[idx] = this.sanitizeBody(model, body)
259
278
  }
279
+
280
+ await this._attachHook('beforeDriverBulkCreateRecord', model, bodies, options)
260
281
  const items = chunk(bodies, chunkSize)
261
282
  for (const item of items) {
262
- await this.bulkCreateRecords(model, item, options)
283
+ await this.bulkCreateRecord(model, item, options)
263
284
  }
285
+ await this._attachHook('afterDriverBulkCreateRecord', model, bodies, [], options)
264
286
  }
265
287
 
266
288
  async _getRecord (model, id, options = {}) {
289
+ await this._attachHook('beforeDriverGetRecord', model, id, options)
267
290
  const result = await this.getRecord(model, id, options)
291
+ await this._attachHook('afterDriverGetRecord', model, id, result, options)
292
+
268
293
  if (isEmpty(result.data) && options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
269
294
  result.data = this.sanitizeRecord(model, result.data)
270
295
  this._injectMeta(result, options)
@@ -283,7 +308,11 @@ async function driverFactory () {
283
308
  }
284
309
  body = this.sanitizeBody(model, body, true)
285
310
  delete body.id
311
+
312
+ await this._attachHook('beforeDriverUpdateRecord', model, id, body, options)
286
313
  const result = await this.updateRecord(model, id, body, options)
314
+ await this._attachHook('afterDriverUpdateRecord', model, id, body, result, options)
315
+
287
316
  if (options.noResult) return
288
317
  result.oldData = this.sanitizeRecord(model, result.oldData)
289
318
  result.data = this.sanitizeRecord(model, result.data)
@@ -304,7 +333,11 @@ async function driverFactory () {
304
333
  }
305
334
  }
306
335
  body = this.sanitizeBody(model, body)
336
+
337
+ await this._attachHook('beforeDriverUpsertRecord', model, body, options)
307
338
  const result = await this.upsertRecord(model, body, options)
339
+ await this._attachHook('afterDriverUpsertRecord', model, body, result, options)
340
+
308
341
  if (options.noResult) return
309
342
  if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
310
343
  result.data = this.sanitizeRecord(model, result.data)
@@ -318,7 +351,11 @@ async function driverFactory () {
318
351
  if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
319
352
  options._data = resp.data
320
353
  }
354
+
355
+ await this._attachHook('beforeDriverRemoveRecord', model, id, options)
321
356
  const result = await this.removeRecord(model, id, options)
357
+ await this._attachHook('afterDriverRemoveRecord', model, id, result, options)
358
+
322
359
  if (options.noResult) return
323
360
  result.oldData = this.sanitizeRecord(model, result.oldData)
324
361
  this._injectMeta(result, options)
@@ -326,13 +363,29 @@ async function driverFactory () {
326
363
  }
327
364
 
328
365
  async _clearRecord (model, options = {}) {
366
+ await this._attachHook('beforeDriverClearRecord', model, options)
329
367
  const result = await this.clearRecord(model, options)
368
+ await this._attachHook('afterDriverClearRecord', model, result, options)
369
+
330
370
  this._injectMeta(result, options)
331
371
  return result
332
372
  }
333
373
 
334
374
  async _findRecord (model, filter = {}, options = {}) {
335
- const result = await this.findRecord(model, filter, options)
375
+ let result
376
+ try {
377
+ await this._attachHook('beforeDriverFindRecord', model, filter, options)
378
+ result = await this.findRecord(model, filter, options)
379
+ await this._attachHook('afterDriverFindRecord', model, filter, result, options)
380
+ } catch (err) {
381
+ if (err.message !== '_emptyColumnQuery') throw err
382
+ result = {
383
+ data: [],
384
+ count: 0
385
+ // warnings: [] // TODO: should generate warnings?
386
+ }
387
+ }
388
+
336
389
  for (const idx in result.data) {
337
390
  result.data[idx] = this.sanitizeRecord(model, result.data[idx])
338
391
  }
@@ -341,7 +394,20 @@ async function driverFactory () {
341
394
  }
342
395
 
343
396
  async _findAllRecord (model, filter = {}, options = {}) {
344
- const result = await this.findAllRecord(model, filter, options)
397
+ let result
398
+ try {
399
+ await this._attachHook('beforeDriverFindAllRecord', model, filter, options)
400
+ result = await this.findAllRecord(model, filter, options)
401
+ await this._attachHook('afterDriverFindAllRecord', model, filter, result, options)
402
+ } catch (err) {
403
+ if (err.message !== '_emptyColumnQuery') throw err
404
+ result = {
405
+ data: [],
406
+ count: 0
407
+ // warnings: [] // TODO: should generate warnings?
408
+ }
409
+ }
410
+
345
411
  for (const idx in result.data) {
346
412
  result.data[idx] = this.sanitizeRecord(model, result.data[idx])
347
413
  }
@@ -350,7 +416,17 @@ async function driverFactory () {
350
416
  }
351
417
 
352
418
  async _countRecord (model, filter = {}, options = {}) {
353
- return await this.countRecord(model, filter, options)
419
+ let result
420
+ try {
421
+ await this._attachHook('beforeDriverCountRecord', model, filter, options)
422
+ result = await this.countRecord(model, filter, options)
423
+ await this._attachHook('afterDriverCountRecord', model, filter, result, options)
424
+ } catch (err) {
425
+ if (err.message !== '_emptyColumnQuery') throw err
426
+ result = { data: 0 }
427
+ }
428
+
429
+ return result
354
430
  }
355
431
 
356
432
  async _createAggregate (model, filter = {}, params = {}, options = {}) {
@@ -367,7 +443,16 @@ async function driverFactory () {
367
443
  if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
368
444
  // if (!fieldPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', field, fieldPropTypes.join(', '))
369
445
 
370
- const result = await this.createAggregate(model, filter, params, options)
446
+ let result
447
+ try {
448
+ await this._attachHook('beforeDriverCreateAggregate', model, filter, params, options)
449
+ result = await this.createAggregate(model, filter, params, options)
450
+ await this._attachHook('afterDriverCreateAggregate', model, filter, params, result, options)
451
+ } catch (err) {
452
+ if (err.message !== '_emptyColumnQuery') throw err
453
+ result = { data: [] }
454
+ }
455
+
371
456
  for (const idx in result.data) {
372
457
  result.data[idx] = this.sanitizeRecord(model, result.data[idx])
373
458
  }
@@ -387,8 +472,17 @@ async function driverFactory () {
387
472
 
388
473
  prop = model.properties.find(p => p.name === field)
389
474
  if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
390
- // if (!fieldPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', field, fieldPropTypes.join(', '))
391
- const result = await this.createHistogram(model, filter, params, options)
475
+
476
+ let result
477
+ try {
478
+ await this._attachHook('beforeDriverCreateHistogram', model, filter, params, options)
479
+ result = await this.createHistogram(model, filter, params, options)
480
+ await this._attachHook('afterDriverCreateHistogram', model, filter, params, result, options)
481
+ } catch (err) {
482
+ if (err.message !== '_emptyColumnQuery') throw err
483
+ result = { data: [] }
484
+ }
485
+
392
486
  for (const idx in result.data) {
393
487
  result.data[idx] = this.sanitizeRecord(model, result.data[idx])
394
488
  }
@@ -402,64 +496,75 @@ async function driverFactory () {
402
496
  }
403
497
 
404
498
  async modelExists (model, options = {}) {
405
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'modelExists')
499
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'modelExists', this.name)
406
500
  }
407
501
 
408
502
  async buildModel (model, options = {}) {
409
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'buildModel')
503
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'buildModel', this.name)
410
504
  }
411
505
 
412
506
  async dropModel (model, options = {}) {
413
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'dropModel')
507
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'dropModel', this.name)
414
508
  }
415
509
 
416
510
  async createRecord (model, body = {}, options = {}) {
417
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'createRecord')
511
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'createRecord', this.name)
418
512
  }
419
513
 
420
514
  async getRecord (model, id, options = {}) {
421
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'getRecord')
515
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'getRecord', this.name)
422
516
  }
423
517
 
424
518
  async updateRecord (model, id, body = {}, options = {}) {
425
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'updateRecord')
519
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'updateRecord', this.name)
426
520
  }
427
521
 
428
522
  async removeRecord (model, id, options = {}) {
429
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'removeRecord')
523
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'removeRecord', this.name)
430
524
  }
431
525
 
432
526
  async clearRecord (model, options = {}) {
433
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'clearRecord')
527
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'clearRecord', this.name)
434
528
  }
435
529
 
436
530
  async findRecord (model, filter = {}, options = {}) {
437
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'findRecord')
531
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'findRecord', this.name)
438
532
  }
439
533
 
440
- async bulkCreateRecords (model, bodies = [], options = {}) {
441
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'bulkCreateRecords')
534
+ async bulkCreateRecord (model, bodies = [], options = {}) {
535
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'bulkCreateRecord', this.name)
442
536
  }
443
537
 
444
538
  async countRecord (model, filter = {}, options = {}) {
445
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'countRecord')
539
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'countRecord', this.name)
446
540
  }
447
541
 
448
542
  async createAggregate (model, filter = {}, params = {}, options = {}) {
449
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'createAggregate')
543
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'createAggregate', this.name)
450
544
  }
451
545
 
452
546
  async createHistogram (model, filter = {}, params = {}, options = {}) {
453
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'createHistogram')
547
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'createHistogram', this.name)
454
548
  }
455
549
 
456
550
  async transaction (model, handler, ...args) {
457
- throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'transaction')
551
+ throw this.plugin.error('notSupportedDriver%s%s%s', this.app.t('method'), 'transaction', this.name)
552
+ }
553
+
554
+ async dispose () {
555
+ await super.dispose()
556
+ }
557
+ }
558
+
559
+ class DoboNullDriver extends DoboDriver {
560
+ constructor (plugin, name = 'null', options = {}) {
561
+ super(plugin, name, options)
562
+ this.memory = true
458
563
  }
459
564
  }
460
565
 
461
566
  this.app.baseClass.DoboDriver = DoboDriver
462
- return DoboDriver
567
+ this.app.baseClass.DoboNullDriver = DoboNullDriver
463
568
  }
464
569
 
465
570
  export default driverFactory
@@ -27,7 +27,6 @@ async function featureFactory () {
27
27
  }
28
28
 
29
29
  this.app.baseClass.DoboFeature = DoboFeature
30
- return DoboFeature
31
30
  }
32
31
 
33
32
  export default featureFactory
@@ -13,17 +13,22 @@ export function cloneOptions (options = {}) {
13
13
 
14
14
  export async function execHook (name, ...args) {
15
15
  const { runHook } = this.app.bajo
16
- const { camelCase, last } = this.app.lib._
16
+ const { camelCase, last, kebabCase } = this.app.lib._
17
17
  const { noHook } = last(args)
18
18
  const { ns } = this.app.dobo
19
+ let [prefix, ...action] = kebabCase(name).split('-')
20
+ action = camelCase(action.join(' '))
19
21
  if (!noHook) {
22
+ if (prefix === 'before') await runHook(`${ns}:beforeAction`, action, this.name, ...args)
20
23
  await runHook(`${ns}:${name}`, this.name, ...args)
24
+ if (prefix === 'after') await runHook(`${ns}:afterAction`, action, this.name, ...args)
25
+ if (prefix === 'before') await runHook(`${ns}.${camelCase(this.name)}:beforeAction`, action, ...args)
21
26
  await runHook(`${ns}.${camelCase(this.name)}:${name}`, ...args)
27
+ if (prefix === 'after') await runHook(`${ns}.${camelCase(this.name)}:afterAction`, action, ...args)
22
28
  }
23
29
  }
24
30
 
25
31
  export async function execModelHook (name, ...args) {
26
- if (['beforeBuildQuery', 'beforeBuildSearch', 'afterBuildQuery', 'afterBuildSearch'].includes(name)) return
27
32
  const { last } = this.app.lib._
28
33
  const { runModelHook } = this.app.dobo
29
34
  const { noModelHook } = last(args)
@@ -62,7 +67,6 @@ export async function execValidation (body, options = {}) {
62
67
  */
63
68
  export async function getFilterAndOptions (filter = {}, options = {}, action) {
64
69
  const { cloneDeep } = this.app.lib._
65
- const { runModelHook } = this.app.dobo
66
70
  const nFilter = cloneDeep(filter || {})
67
71
  const nOptions = cloneOptions.call(this, options)
68
72
  if (options.noMagic) {
@@ -81,12 +85,8 @@ export async function getFilterAndOptions (filter = {}, options = {}, action) {
81
85
  nOptions.throwNotFound = nOptions.throwNotFound ?? true
82
86
  nFilter.orgQuery = nFilter.query
83
87
  nFilter.orgSearch = nFilter.search
84
- if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuildQuery', nFilter.query, nOptions)
85
88
  nFilter.query = buildFilterQuery.call(this, nFilter) ?? {}
86
- if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildQuery', nFilter.query, nOptions)
87
- if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuilSearch', nFilter.search, nOptions)
88
89
  nFilter.search = buildFilterSearch.call(this, nFilter) ?? {}
89
- if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildSearch', nFilter.search, nOptions)
90
90
  const { limit, page, skip, sort } = preparePagination.call(this, nFilter, nOptions)
91
91
  nFilter.limit = limit
92
92
  nFilter.page = page
@@ -135,7 +135,7 @@ export async function getAttachmentPath (id, field, file, options = {}) {
135
135
 
136
136
  export async function copyAttachment (id, options = {}) {
137
137
  if (!this.app.waibu) return
138
- if (!this.attachment) return
138
+ if (!this.options.attachment) return
139
139
  const { fs } = this.app.lib
140
140
  const { req, setField, setFile, mimeType, stats } = options
141
141
  const { dir, files } = await this.app.waibu.getUploadedFiles(req.id, false, true)
@@ -158,7 +158,7 @@ export async function copyAttachment (id, options = {}) {
158
158
  }
159
159
 
160
160
  export async function handleAttachmentUpload (id, trigger, options = {}) {
161
- if (!this.attachment) return
161
+ if (!this.options.attachment) return
162
162
  const { getPluginDataDir } = this.app.bajo
163
163
  const { fs } = this.app.lib
164
164
  const { req, mimeType, stats, setFile, setField } = options
@@ -170,53 +170,7 @@ export async function handleAttachmentUpload (id, trigger, options = {}) {
170
170
  return copyAttachment.call(this, id, { req, mimeType, stats, setFile, setField })
171
171
  }
172
172
 
173
- async function _getRef ({ ref, rModel, prop, key, options, filter } = {}) {
174
- if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) return
175
- if (ref.fields.length === 0) return
176
- const { fmt } = options
177
- const fields = [...ref.fields]
178
- if (!fields.includes(prop.name)) fields.push(prop.name)
179
- const rOptions = { dataOnly: true, refs: [], fmt, fields }
180
- const results = await rModel.findRecord(filter, rOptions)
181
- return { rOptions, results }
182
- }
183
-
184
- export async function getSingleRef (record = {}, options = {}) {
185
- const { isSet } = this.app.lib.aneka
186
- const { parseQuery } = this.app.dobo
187
- const { get } = this.app.lib._
188
- const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
189
- const refs = {}
190
- options.refs = options.refs ?? []
191
- if (props.length > 0) {
192
- for (const prop of props) {
193
- for (const key in prop.ref) {
194
- try {
195
- if (get(record, `_ref.${key}`)) continue
196
- const ref = prop.ref[key]
197
- const rModel = this.app.dobo.getModel(ref.model, true)
198
- if (!rModel) return
199
- let query = {}
200
- query[ref.field] = record[prop.name]
201
- if (ref.field === 'id') query[ref.field] = this.sanitizeId(query[ref.field])
202
- if (ref.query) query = { $and: [query, parseQuery(ref.query, rModel)] }
203
- const filter = { query }
204
- const resp = await _getRef.call(this, { ref, rModel, prop, key, options, filter })
205
- if (!resp) continue
206
- const { rOptions, results } = resp
207
- const data = []
208
- for (const res of results) {
209
- data.push(await rModel.sanitizeRecord(res, rOptions))
210
- }
211
- refs[key] = ['1:1'].includes(ref.type) ? data[0] : data
212
- } catch (err) {}
213
- }
214
- }
215
- }
216
- record._ref = refs
217
- }
218
-
219
- export async function getMultiRefs (records = [], options = {}) {
173
+ export async function getRefs (records = [], options = {}) {
220
174
  const { isSet } = this.app.lib.aneka
221
175
  const { uniq, without, get } = this.app.lib._
222
176
  const { parseQuery } = this.app.dobo
@@ -226,30 +180,46 @@ export async function getMultiRefs (records = [], options = {}) {
226
180
  for (const prop of props) {
227
181
  for (const key in prop.ref) {
228
182
  try {
229
- if (get(records, `0._ref.${key}`)) continue
183
+ if (records.length === 0) return
184
+ const isValues = Array.isArray(records[0][prop.name])
185
+ if (get(records, `0._ref.${key}`)) return
230
186
  const ref = prop.ref[key]
231
187
  const rModel = this.app.dobo.getModel(ref.model, true)
232
188
  if (!rModel) return
233
189
  let matches = []
234
- for (const r of records) {
235
- matches.push(prop.name === 'id' ? rModel.sanitizeId(r[prop.name]) : r[prop.name])
190
+ for (const rec of records) {
191
+ const items = isValues ? [...rec[prop.name]] : [rec[prop.name]]
192
+ matches.push(...items.map(item => prop.name === 'id' ? rModel.sanitizeId(item) : item))
236
193
  }
237
194
  matches = uniq(without(matches, undefined, null, NaN)).map(i => i + '')
238
195
  let query = {}
239
196
  query[ref.field] = { $in: matches }
197
+
198
+ const siteIdProp = this.properties.find(item => item.name === 'siteId')
199
+ const siteIdRProp = rModel.properties.find(item => item.name === 'siteId')
200
+ if (siteIdProp && siteIdRProp) query.siteId = records[0].siteId
201
+
240
202
  if (ref.query) query = { $and: [query, parseQuery(ref.query, rModel)] }
241
203
  const filter = { query, limit: matches.length }
242
- const resp = await _getRef.call(this, { ref, rModel, prop, key, options, filter })
243
- if (!resp) continue
244
- const { rOptions, results } = resp
204
+ if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) return
205
+ if (ref.fields.length === 0) return
206
+ const { fmt, req } = options
207
+ const fields = [...ref.fields]
208
+ if (!fields.includes(prop.name)) fields.push(prop.name)
209
+ const rOptions = { dataOnly: true, refs: [], fmt, req, fields }
210
+ const results = await rModel.findRecord(filter, rOptions)
245
211
  for (const i in records) {
246
212
  records[i]._ref = records[i]._ref ?? {}
247
213
  const rec = records[i]
248
- const res = results.find(res => (res[ref.field] + '') === rec[prop.name] + '')
249
- if (res) records[i]._ref[key] = await rModel.sanitizeRecord(res, rOptions)
250
- else records[i]._ref[key] = {}
214
+ let items = isValues ? [...rec[prop.name]] : [rec[prop.name]]
215
+ items = items.map(item => item + '')
216
+ const res = results.filter(r => items.includes(r[ref.field] + ''))
217
+ if (res.length === 0) records[i]._ref[key] = isValues ? [] : {}
218
+ else records[i]._ref[key] = isValues ? res : res[0]
251
219
  }
252
- } catch (err) {}
220
+ } catch (err) {
221
+ if (this.app.bajo.config.log.level === 'trace') console.error(err)
222
+ }
253
223
  }
254
224
  }
255
225
  }
@@ -261,7 +231,7 @@ export function buildFilterQuery (filter = {}) {
261
231
  return sanitizeQuery.call(this, query)
262
232
  }
263
233
 
264
- function sanitizeQuery (query = {}, parent) {
234
+ export function sanitizeQuery (query = {}, parent) {
265
235
  const { isPlainObject, isArray, find, cloneDeep } = this.app.lib._
266
236
  const { isSet } = this.app.lib.aneka
267
237
  const { dayjs } = this.app.lib
@@ -1,9 +1,9 @@
1
1
  import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook } from './_util.js'
2
2
 
3
3
  export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
4
- const action = 'bulkCreateRecords'
4
+ const action = 'bulkCreateRecord'
5
5
 
6
- async function bulkCreateRecords (...args) {
6
+ async function bulkCreateRecord (...args) {
7
7
  if (args.length === 0) return this.action(action, ...args)
8
8
  const [bodies = [], opts = {}] = args
9
9
  const { cloneDeep, get } = this.app.lib._
@@ -17,9 +17,9 @@ async function bulkCreateRecords (...args) {
17
17
  inputs[idx] = await this.sanitizeBody({ body: inputs[idx], extFields, strict: true, truncateString, onlyTypes })
18
18
  }
19
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)
20
+ await execHook.call(this, 'beforeBulkCreateRecord', inputs, options)
21
+ await execModelHook.call(this, 'beforeBulkCreateRecord', inputs, options)
22
+ await execDynHook.call(this, 'beforeBulkCreateRecord', inputs, options)
23
23
  if (!noValidation) {
24
24
  for (const input of inputs) {
25
25
  await execValidation.call(this, input, options)
@@ -27,10 +27,10 @@ async function bulkCreateRecords (...args) {
27
27
  }
28
28
  // TODO: bulk don't return anything currently, it should return at least a stat
29
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)
30
+ await execDynHook.call(this, 'afterBulkCreateRecord', inputs, options)
31
+ await execModelHook.call(this, 'afterBulkCreateRecord', inputs, options)
32
+ await execHook.call(this, 'afterBulkCreateRecord', inputs, options)
33
33
  return []
34
34
  }
35
35
 
36
- export default bulkCreateRecords
36
+ export default bulkCreateRecord
@@ -4,7 +4,7 @@ const action = 'createAttachment'
4
4
  async function createAttachment (...args) {
5
5
  const { createThumbnail } = this.app.bajoExtra
6
6
  const { thumbSizes: size } = this.app.dobo.config.default.attachment
7
- if (!this.attachment) return
7
+ if (!this.options.attachment) return
8
8
  if (args.length === 0) return this.action(action, ...args)
9
9
  const [id, opts = {}] = args
10
10
  const { fs } = this.app.lib
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getRefs, handleReq } from './_util.js'
2
2
 
3
3
  export const onlyTypes = ['datetime', 'date', 'time', 'timestamp', 'array', 'object']
4
4
  const action = 'createRecord'
@@ -25,7 +25,7 @@ async function createRecord (...args) {
25
25
  result = result ?? {}
26
26
  const { warnings } = getDefaultValues(options)
27
27
  if (!warnings) delete result.warnings
28
- if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
28
+ if (isSet(options.refs)) await getRefs.call(this, [result.data], options)
29
29
  if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
30
30
  await execDynHook.call(this, 'afterCreateRecord', input, result, options)
31
31
  await execModelHook.call(this, 'afterCreateRecord', input, result, options)
@@ -1,4 +1,4 @@
1
- import { getMultiRefs, execHook, execModelHook, execDynHook, getFilterAndOptions, cloneOptions } from './_util.js'
1
+ import { getRefs, execHook, execModelHook, execDynHook, getFilterAndOptions, cloneOptions, sanitizeQuery } from './_util.js'
2
2
  const action = 'findAllRecord'
3
3
 
4
4
  async function native (...args) {
@@ -13,9 +13,10 @@ async function native (...args) {
13
13
  const { hardCap, warnings } = getDefaultValues(options)
14
14
  if (dataOnly) options.count = false
15
15
  const { noResultSanitizer } = options
16
- await execHook.call(this, 'beforeFindRecord', filter, options)
17
- await execModelHook.call(this, 'beforeFindRecord', filter, options)
18
- await execDynHook.call(this, 'beforeFindRecord', filter, options)
16
+ await execHook.call(this, 'beforeFindAllRecord', filter, options)
17
+ await execModelHook.call(this, 'beforeFindAllRecord', filter, options)
18
+ await execDynHook.call(this, 'beforeFindAllRecord', filter, options)
19
+ filter.query = sanitizeQuery.call(this, filter.query)
19
20
  const cFilter = cloneDeep(filter)
20
21
  if (get) {
21
22
  const resp = await get({ model: this, action, filter: cFilter, options })
@@ -37,15 +38,15 @@ async function native (...args) {
37
38
  result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
38
39
  if (!warnings) delete result.warnings
39
40
 
40
- if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
41
+ if (isSet(options.refs)) await getRefs.call(this, result.data, options)
41
42
  if (!noResultSanitizer) {
42
43
  for (const idx in result.data) {
43
44
  result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
44
45
  }
45
46
  }
46
- await execDynHook.call(this, 'afterFindRecord', filter, result, options)
47
- await execModelHook.call(this, 'afterFindRecord', filter, result, options)
48
- await execHook.call(this, 'afterFindRecord', filter, result, options)
47
+ await execDynHook.call(this, 'afterFindAllRecord', filter, result, options)
48
+ await execModelHook.call(this, 'afterFindAllRecord', filter, result, options)
49
+ await execHook.call(this, 'afterFindAllRecord', filter, result, options)
49
50
  if (set) await set({ model: this, action, filter: cFilter, options, result })
50
51
  return dataOnly ? result.data : result
51
52
  }
@@ -2,7 +2,7 @@ import { mergeAttachmentInfo } from './_util.js'
2
2
  const action = 'findAttachment'
3
3
 
4
4
  async function findAttachment (...args) {
5
- if (!this.attachment) return
5
+ if (!this.options.attachment) return
6
6
  if (args.length === 0) return this.action(action, ...args)
7
7
  const [id, opts = {}] = args
8
8
  const { fastGlob, fs } = this.app.lib
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, execDynHook, getMultiRefs } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, getRefs, sanitizeQuery } from './_util.js'
2
2
  const action = 'findRecord'
3
3
 
4
4
  /**
@@ -80,6 +80,7 @@ async function findRecord (...args) {
80
80
  await execHook.call(this, 'beforeFindRecord', filter, options)
81
81
  await execModelHook.call(this, 'beforeFindRecord', filter, options)
82
82
  await execDynHook.call(this, 'beforeFindRecord', filter, options)
83
+ filter.query = sanitizeQuery.call(this, filter.query)
83
84
  const cFilter = cloneDeep(filter)
84
85
  if (get) {
85
86
  const resp = await get({ model: this, action, filter: cFilter, options })
@@ -101,7 +102,7 @@ async function findRecord (...args) {
101
102
  }
102
103
  result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
103
104
  if (!warnings) delete result.warnings
104
- if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
105
+ if (isSet(options.refs)) await getRefs.call(this, result.data, options)
105
106
  if (!noResultSanitizer) {
106
107
  for (const idx in result.data) {
107
108
  result.data[idx] = await this.sanitizeRecord(result.data[idx], options)