dobo 2.19.1 → 2.20.1

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.
@@ -52,12 +52,12 @@ async function sanitizeProp (model, prop, indexes) {
52
52
  * @param {Array} [inputs] - Array of properties
53
53
  * @param {Array} [indexes] - Container array to fill up found index
54
54
  */
55
- async function findAllProps (model, inputs = [], indexes = [], isExtender) {
55
+ async function findAllProps (model, inputs = [], indexes = []) {
56
56
  const { isPlainObject, cloneDeep } = this.app.lib._
57
57
  const isIdProp = inputs.find(p => {
58
58
  return isPlainObject(p) ? p.name === 'id' : p.startsWith('id,')
59
59
  })
60
- if (!isExtender && !isIdProp) {
60
+ if (!isIdProp) {
61
61
  const idField = cloneDeep(model.connection.driver.idField)
62
62
  idField.name = 'id'
63
63
  inputs.unshift(idField)
@@ -74,7 +74,7 @@ async function findAllProps (model, inputs = [], indexes = [], isExtender) {
74
74
  * @param {Object} options - Options to the feature
75
75
  * @returns {Array} New properties found in feature
76
76
  */
77
- async function applyFeature (model, feature, options, indexes, isExtender) {
77
+ async function applyFeature (model, feature, options, indexes) {
78
78
  const { isArray, findIndex } = this.app.lib._
79
79
  if (feature.name === 'dobo:unique' && options.field === 'id') {
80
80
  const idx = findIndex(model.properties, { name: 'id' })
@@ -103,14 +103,14 @@ async function applyFeature (model, feature, options, indexes, isExtender) {
103
103
  * @param {Object} model - Model
104
104
  * @param {Array} [inputs] - Array of properties
105
105
  */
106
- async function findAllFeats (model, inputs = [], indexes = [], isExtender) {
106
+ async function findAllFeats (model, inputs = [], indexes = []) {
107
107
  const { isString, omit } = this.app.lib._
108
108
  for (let feat of inputs) {
109
109
  if (isString(feat)) feat = { name: feat }
110
110
  const featName = feat.name.indexOf(':') === -1 ? `dobo:${feat.name}` : feat.name
111
111
  const feature = this.app.dobo.getFeature(featName)
112
112
  if (!feature) this.fatal('invalidFeature%s%s', model.name, featName)
113
- await applyFeature.call(this, model, feature, omit(feat, 'name'), indexes, isExtender)
113
+ await applyFeature.call(this, model, feature, omit(feat, 'name'), indexes)
114
114
  }
115
115
  }
116
116
 
@@ -235,9 +235,7 @@ export async function sanitizeAll (model) {
235
235
  * @returns {Object} Sanitized model
236
236
  */
237
237
  async function createSchema (item) {
238
- const { readConfig } = this.app.bajo
239
- const { fastGlob } = this.app.lib
240
- const { find, isPlainObject, orderBy, get, cloneDeep } = this.app.lib._
238
+ const { find, orderBy, get } = this.app.lib._
241
239
  const { mergeObjectsByKey, defaultsDeep, parseObject } = this.app.lib.aneka
242
240
  if (item.file && !item.base) item.base = path.basename(item.file, path.extname(item.file))
243
241
  item.attachment = item.attachment ?? true
@@ -265,29 +263,18 @@ async function createSchema (item) {
265
263
  if (!item.connection && conn === 'default') item.connection = this.getConnection('memory')
266
264
  }
267
265
  if (!item.connection) this.fatal('unknownConn%s%s', conn, item.name)
268
- const defCache = cloneDeep(get(this, 'app.bajoCache.config.default', this.config.default.cache))
269
- item.cache = item.cache ?? item.connection.options.cache ?? defCache
270
- if (item.cache === true) item.cache = get(item, 'connection.options.cache', defCache)
271
- else if (item.cache === false) item.cache = { ttlDur: 0 }
272
- item.cache = parseObject(defaultsDeep(get(this, `app.bajoCache.config.dobo.${item.name}`), item.cache))
266
+ // cache settings
267
+ const defCache = defaultsDeep({}, item.connection.options.cache, get(this, 'app.bajoCache.config.default', this.config.default.cache))
268
+ if (item.cache === false) item.cache = { ttlDur: 0 }
269
+ else if (item.cache === true) item.cache = defCache
270
+ else item.cache = defaultsDeep({}, item.cache, defCache)
271
+ item.cache = parseObject(item.cache)
272
+ if (item.connection.name === 'memory') item.cache.ttlDur = 0
273
+
274
+ // let's run
273
275
  await findAllProps.call(this, item, props, indexes)
274
276
  await findAllFeats.call(this, item, feats, indexes)
275
277
  await findAllIndexes.call(this, item, indexes)
276
- // item extender
277
- if (item.base) {
278
- for (const ns of this.app.getAllNs()) {
279
- const plugin = this.app[ns]
280
- const glob = `${plugin.dir.pkg}/extend/dobo/extend/${item.ns}/model/${item.base}.*`
281
- const files = await fastGlob(glob)
282
- for (const file of files) {
283
- const extender = await readConfig(file, { ns: plugin.ns, ignoreError: true })
284
- if (!isPlainObject(extender)) this.plugin.fatal('invalidModelExtender%s%s', ns, item.name)
285
- await findAllProps.call(this, item, extender.properties ?? [], indexes, true)
286
- await findAllFeats.call(this, item, extender.features ?? [], indexes, true)
287
- await findAllIndexes.call(this, item, extender.indexes ?? [], indexes, true)
288
- }
289
- }
290
- }
291
278
  for (const key of ['properties', 'indexes']) {
292
279
  item[key] = mergeObjectsByKey(item[key], 'name')
293
280
  }
@@ -322,10 +309,11 @@ async function collectModels () {
322
309
 
323
310
  const base = path.basename(file, path.extname(file))
324
311
  const defName = pascalCase(`${this.alias} ${base}`)
325
- const item = await readConfig(file, { ns: this.ns, ignoreError: true })
312
+ const item = await readConfig(file, { ns: this.ns, baseNs: me.ns })
326
313
  if (isEmpty(item)) return undefined
327
314
  if (!isPlainObject(item)) me.fatal('invalidModel%s', defName)
328
315
  item.name = item.name ?? defName
316
+ me.log.trace('- %s', item.name)
329
317
  item.collName = item.collName ?? item.name
330
318
  item.file = file
331
319
  item.ns = this.ns
@@ -344,7 +332,6 @@ async function collectModels () {
344
332
  }
345
333
  for (const model of me.models) {
346
334
  await sanitizeRef.call(this, model, me.models)
347
- me.log.trace('- %s', model.name)
348
335
  }
349
336
  this.log.debug('collected%s%d', this.t('model'), this.models.length)
350
337
  }
@@ -430,3 +430,12 @@ export function preparePagination (filter = {}, options = {}) {
430
430
  const sort = buildSort(sortInput, options.allowSortUnindexed)
431
431
  return { limit, page, skip, sort }
432
432
  }
433
+
434
+ export async function clearCache (id) {
435
+ const { clear } = this.app.bajoCache
436
+ if (!clear) return
437
+ await clear({ key: `dobo|${this.name}|getRecord|${id}` })
438
+ await clear({ key: `dobo|${this.name}|findRecord` })
439
+ await clear({ key: `dobo|${this.name}|findAllRecord` })
440
+ await clear({ key: `dobo|${this.name}|findOneRecord` })
441
+ }
@@ -4,45 +4,50 @@ const action = 'findAllRecord'
4
4
  async function native (...args) {
5
5
  const { isSet } = this.app.lib.aneka
6
6
  const { getDefaultValues, t } = this.app.dobo
7
- const { pick, omit } = this.app.lib._
7
+ const { pick, omit, cloneDeep } = this.app.lib._
8
8
  const [params = {}, opts = {}] = args
9
+ const { get, set } = this.app.bajoCache ?? {}
9
10
  opts.dataOnly = opts.dataOnly ?? true
10
11
  const { dataOnly } = opts
11
12
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
12
13
  const { hardCap, warnings } = getDefaultValues(options)
13
14
  if (dataOnly) options.count = false
14
15
  const { noResultSanitizer } = options
15
- try {
16
- await execHook.call(this, 'beforeFindRecord', filter, options)
17
- await execModelHook.call(this, 'beforeFindRecord', filter, options)
18
- await execDynHook.call(this, 'beforeFindRecord', filter, options)
19
- let result = options.record ?? (await this.driver._findAllRecord(this, filter, options)) ?? {}
20
- result.limit = filter.limit
21
- result.filter = pick(filter, ['query', 'match', 'sort'])
22
- result.warnings = result.warnings ?? []
23
- if (!options.count) result = omit(result, ['count', 'pages'])
24
- else if (options.count && result.count > hardCap) {
25
- result.warnings.push(t('hardCapWarning%s%s', result.count, hardCap))
26
- result.count = hardCap
27
- result.hardCapped = true
16
+ await execHook.call(this, 'beforeFindRecord', filter, options)
17
+ await execModelHook.call(this, 'beforeFindRecord', filter, options)
18
+ await execDynHook.call(this, 'beforeFindRecord', filter, options)
19
+ const cFilter = cloneDeep(filter)
20
+ if (get) {
21
+ const resp = await get({ model: this, action, filter: cFilter, options })
22
+ if (resp) {
23
+ resp.cached = true
24
+ return dataOnly ? resp.data : resp
28
25
  }
29
- result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
30
- if (!warnings) delete result.warnings
26
+ }
27
+ let result = options.record ?? (await this.driver._findAllRecord(this, filter, options)) ?? {}
28
+ result.limit = filter.limit
29
+ result.filter = pick(filter, ['query', 'match', 'sort'])
30
+ result.warnings = result.warnings ?? []
31
+ if (!options.count) result = omit(result, ['count', 'pages'])
32
+ else if (options.count && result.count > hardCap) {
33
+ result.warnings.push(t('hardCapWarning%s%s', result.count, hardCap))
34
+ result.count = hardCap
35
+ result.hardCapped = true
36
+ }
37
+ result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
38
+ if (!warnings) delete result.warnings
31
39
 
32
- if (!noResultSanitizer) {
33
- for (const idx in result.data) {
34
- result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
35
- }
40
+ if (!noResultSanitizer) {
41
+ for (const idx in result.data) {
42
+ result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
36
43
  }
37
- if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
38
- await execDynHook.call(this, 'afterFindRecord', filter, result, options)
39
- await execModelHook.call(this, 'afterFindRecord', filter, result, options)
40
- await execHook.call(this, 'afterFindRecord', filter, result, options)
41
- return dataOnly ? result.data : result
42
- } catch (err) {
43
- if (err.code === 'cachedResult') return err.data
44
- throw err
45
44
  }
45
+ if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
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)
49
+ if (set) await set({ model: this, action, filter: cFilter, options, result })
50
+ return dataOnly ? result.data : result
46
51
  }
47
52
 
48
53
  async function loop (...args) {
@@ -69,45 +69,50 @@ async function findRecord (...args) {
69
69
  const { getDefaultValues, t } = this.app.dobo
70
70
  const [params = {}, opts = {}] = args
71
71
  const { isSet } = this.app.lib.aneka
72
- const { pick, omit } = this.app.lib._
72
+ const { pick, omit, cloneDeep } = this.app.lib._
73
+ const { get, set } = this.app.bajoCache ?? {}
73
74
  opts.dataOnly = opts.dataOnly ?? true
74
75
  const { dataOnly } = opts
75
76
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
76
77
  const { hardCap, warnings } = getDefaultValues(options)
77
78
  if (dataOnly) options.count = false
78
79
  const { noResultSanitizer } = options
79
- try {
80
- await execHook.call(this, 'beforeFindRecord', filter, options)
81
- await execModelHook.call(this, 'beforeFindRecord', filter, options)
82
- await execDynHook.call(this, 'beforeFindRecord', filter, options)
83
- let result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
84
- result.page = filter.page
85
- result.limit = filter.limit
86
- result.filter = pick(filter, ['query', 'match', 'sort'])
87
- result.warnings = result.warnings ?? []
88
- if (!options.count) result = omit(result, ['count', 'pages'])
89
- else if (options.count && result.count > hardCap) {
90
- result.warnings.push(t('hardCapWarning%s%s', result.count, hardCap))
91
- result.count = hardCap
92
- result.hardCapped = true
80
+ await execHook.call(this, 'beforeFindRecord', filter, options)
81
+ await execModelHook.call(this, 'beforeFindRecord', filter, options)
82
+ await execDynHook.call(this, 'beforeFindRecord', filter, options)
83
+ const cFilter = cloneDeep(filter)
84
+ if (get) {
85
+ const resp = await get({ model: this, action, filter: cFilter, options })
86
+ if (resp) {
87
+ resp.cached = true
88
+ return dataOnly ? resp.data : resp
93
89
  }
94
- result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
95
- if (!warnings) delete result.warnings
90
+ }
91
+ let result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
92
+ result.page = filter.page
93
+ result.limit = filter.limit
94
+ result.filter = pick(filter, ['query', 'search', 'sort'])
95
+ result.warnings = result.warnings ?? []
96
+ if (!options.count) result = omit(result, ['count', 'pages'])
97
+ else if (options.count && result.count > hardCap) {
98
+ result.warnings.push(t('hardCapWarning%s%s', result.count, hardCap))
99
+ result.count = hardCap
100
+ result.hardCapped = true
101
+ }
102
+ result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
103
+ if (!warnings) delete result.warnings
96
104
 
97
- if (!noResultSanitizer) {
98
- for (const idx in result.data) {
99
- result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
100
- }
105
+ if (!noResultSanitizer) {
106
+ for (const idx in result.data) {
107
+ result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
101
108
  }
102
- if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
103
- await execDynHook.call(this, 'afterFindRecord', filter, result, options)
104
- await execModelHook.call(this, 'afterFindRecord', filter, result, options)
105
- await execHook.call(this, 'afterFindRecord', filter, result, options)
106
- return dataOnly ? result.data : result
107
- } catch (err) {
108
- if (err.code === 'cachedResult') return err.data
109
- throw err
110
109
  }
110
+ if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
111
+ await execDynHook.call(this, 'afterFindRecord', filter, result, options)
112
+ await execModelHook.call(this, 'afterFindRecord', filter, result, options)
113
+ await execHook.call(this, 'afterFindRecord', filter, result, options)
114
+ if (set) await set({ model: this, action, filter: cFilter, options, result })
115
+ return dataOnly ? result.data : result
111
116
  }
112
117
 
113
118
  export default findRecord
@@ -48,29 +48,33 @@ async function getRecord (...args) {
48
48
  const { getDefaultValues } = this.app.dobo
49
49
  const { isEmpty } = this.app.lib._
50
50
  const { isSet } = this.app.lib.aneka
51
+ const { get, set } = this.app.bajoCache ?? {}
51
52
  opts.dataOnly = opts.dataOnly ?? true
52
53
  const { dataOnly } = opts
53
54
  const { options } = await getFilterAndOptions.call(this, null, opts, action)
54
55
  const { noResultSanitizer } = options
55
56
  id = this.sanitizeId(id)
56
- try {
57
- await execHook.call(this, 'beforeGetRecord', id, options)
58
- await execModelHook.call(this, 'beforeGetRecord', id, options)
59
- await execDynHook.call(this, 'beforeGetRecord', id, options)
60
- const result = options.record ?? (await this.driver._getRecord(this, id, options)) ?? {}
61
- const { warnings } = getDefaultValues(options)
62
- if (!warnings) delete result.warnings
63
- if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
64
- if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
65
- if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
66
- await execDynHook.call(this, 'afterGetRecord', id, result, options)
67
- await execModelHook.call(this, 'afterGetRecord', id, result, options)
68
- await execHook.call(this, 'afterGetRecord', id, result, options)
69
- return dataOnly ? result.data : result
70
- } catch (err) {
71
- if (err.code === 'cachedResult') return err.data
72
- throw err
57
+ await execHook.call(this, 'beforeGetRecord', id, options)
58
+ await execModelHook.call(this, 'beforeGetRecord', id, options)
59
+ await execDynHook.call(this, 'beforeGetRecord', id, options)
60
+ if (get) {
61
+ const resp = await get({ model: this, action, id, options })
62
+ if (resp) {
63
+ resp.cached = true
64
+ return dataOnly ? resp.data : resp
65
+ }
73
66
  }
67
+ const result = options.record ?? (await this.driver._getRecord(this, id, options)) ?? {}
68
+ const { warnings } = getDefaultValues(options)
69
+ if (!warnings) delete result.warnings
70
+ if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
71
+ if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
72
+ if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
73
+ await execDynHook.call(this, 'afterGetRecord', id, result, options)
74
+ await execModelHook.call(this, 'afterGetRecord', id, result, options)
75
+ await execHook.call(this, 'afterGetRecord', id, result, options)
76
+ if (set) await set({ model: this, action, id, options, result })
77
+ return dataOnly ? result.data : result
74
78
  }
75
79
 
76
80
  export default getRecord
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, getSingleRef, handleReq, clearCache } from './_util.js'
2
2
  const action = 'removeRecord'
3
3
 
4
4
  /**
@@ -45,6 +45,7 @@ async function removeRecord (...args) {
45
45
  await execModelHook.call(this, 'beforeRemoveRecord', id, options)
46
46
  await execDynHook.call(this, 'beforeRemoveRecord', id, options)
47
47
  const result = options.record ?? (await this.driver._removeRecord(this, id, options)) ?? {}
48
+ await clearCache.call(this, id)
48
49
  if (noResult) return
49
50
  const { warnings } = getDefaultValues(options)
50
51
  if (!warnings) delete result.warnings
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq, clearCache } from './_util.js'
2
2
  import { onlyTypes } from './create-record.js'
3
3
  const action = 'updateRecord'
4
4
 
@@ -67,6 +67,7 @@ async function updateRecord (...args) {
67
67
  await execDynHook.call(this, 'beforeUpdateRecord', id, input, options)
68
68
  if (!noValidation) await execValidation.call(this, input, options)
69
69
  const result = options.record ?? (await this.driver._updateRecord(this, id, input, options)) ?? {}
70
+ await clearCache.call(this, id)
70
71
  if (noResult) return
71
72
  const { warnings } = getDefaultValues(options)
72
73
  if (!warnings) delete result.warnings
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, execDynHook, execValidation, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, execValidation, getSingleRef, handleReq, clearCache } from './_util.js'
2
2
  const action = 'upsertRecord'
3
3
 
4
4
  async function native (body = {}, opts = {}) {
@@ -16,6 +16,7 @@ async function native (body = {}, opts = {}) {
16
16
  await execModelHook.call(this, 'beforeUpsertRecord', input, options)
17
17
  await execDynHook.call(this, 'beforeUpsertRecord', input, options)
18
18
  const result = options.record ?? (await this.driver._upsertRecord(this, input, options)) ?? {}
19
+ await clearCache.call(this, body.id)
19
20
  if (noResult) return
20
21
  const { warnings } = getDefaultValues(options)
21
22
  if (!warnings) delete result.warnings
@@ -39,7 +40,7 @@ async function manual (body = {}, options = {}) {
39
40
  } catch (err) {
40
41
  }
41
42
  }
42
- const id = get(old, 'dara.id')
43
+ const id = get(old, 'data.id')
43
44
  if (id) return await this.updateRecord(old.data.id, omit(body, ['id']), options)
44
45
  return await this.createRecord(body, options)
45
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.19.1",
3
+ "version": "2.20.1",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-04-23
4
+
5
+ - [2.20.1] Bug fix in ```collect-models.js```, now with deep merge in advance
6
+
3
7
  ## 2026-04-21
4
8
 
5
- - [2.5.1] Bug fix in ```collect-models.js```, now values are sanitized with ```parseObject()```
6
- - [2.5.1] Bug fix in ```options.dataOnly``` on all model methods
7
- - [2.5.1] Add ```options.noMagic```
9
+ - [2.19.1] Bug fix in ```collect-models.js```, now values are sanitized with ```parseObject()```
10
+ - [2.19.1] Bug fix in ```options.dataOnly``` on all model methods
11
+ - [2.19.1] Add ```options.noMagic```
12
+ - [2.20.0] Revert back to NOT using hooks for caching
8
13
 
9
14
  ## 2026-04-19
10
15