dobo 2.11.4 → 2.13.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/.bootorder ADDED
@@ -0,0 +1 @@
1
+ 10
@@ -146,7 +146,7 @@
146
146
  "buildOp": "'build' operation",
147
147
  "dropOp": "'drop' operation",
148
148
  "maxLimitWarning%s%s": "Records per page (%s rows) above the allowed threshold (%s rows)",
149
- "hardLimitWarning%s%s": "Max records returned (%s rows) above the allowed threshold (%s rows)",
149
+ "hardCapWarning%s%s": "Max records returned (%s rows) above the allowed threshold (%s rows)",
150
150
  "maxPageError%s%s": "Page number (%s) above the allowed threshold (%s)",
151
151
  "field": {
152
152
  "id": "ID",
@@ -143,8 +143,8 @@
143
143
  "inMemoryDb%s%s": "'%s' adalah database in-memory, %s tidak diizinkan",
144
144
  "buildOp": "operasi 'build'",
145
145
  "dropOp": "operasi 'drop'",
146
- "maxLimitWarning%s": "Data per halaman (%s baris) melampaui batas yang diijinkan (%s baris)",
147
- "hardLimitWarning%s": "Maksimum data yang dihasilkan (%s baris) melampaui batas yang diijinkan (%s baris)",
146
+ "maxLimitWarning%s%s": "Data per halaman (%s baris) melampaui batas yang diijinkan (%s baris)",
147
+ "hardCapWarning%s%s": "Maksimum data yang dihasilkan (%s baris) melampaui batas yang diijinkan (%s baris)",
148
148
  "maxPageError%s%s": "Nomor halaman (%s) melampaui batas yang diijinkan (%s)",
149
149
  "field": {
150
150
  "id": "ID",
package/index.js CHANGED
@@ -148,10 +148,10 @@ async function factory (pkgName) {
148
148
  },
149
149
  default: {
150
150
  filter: {
151
- limit: 25,
152
- maxLimit: 200,
153
- hardLimit: 10000,
154
- maxPage: 50,
151
+ limit: 25, // num of records per page
152
+ maxLimit: 200, // max num of records per page
153
+ hardCap: 10000, // max returned records
154
+ maxPage: 50, // max allowed page number
155
155
  sort: ['dt:-1', 'updatedAt:-1', 'updated_at:-1', 'createdAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
156
156
  },
157
157
  cache: {
@@ -493,6 +493,29 @@ async function factory (pkgName) {
493
493
  else await hook.handler.call(model, ...args)
494
494
  }
495
495
  }
496
+
497
+ getDefaultValues = (options = {}) => {
498
+ const { get } = this.app.lib._
499
+ const config = this.app.dobo.config
500
+ const limit = get(options, 'req.site.setting.dobo.default.limit', config.default.filter.limit)
501
+ const maxLimit = get(options, 'req.site.setting.dobo.default.maxLimit', config.default.filter.maxLimit)
502
+ const hardCap = get(options, 'req.site.setting.dobo.default.hardCap', config.default.filter.hardCap)
503
+ const maxPage = get(options, 'req.site.setting.dobo.default.maxPage', config.default.filter.maxPage)
504
+ const t = options.req ? options.req.t : this.t
505
+ return { limit, maxLimit, hardCap, maxPage, t }
506
+ }
507
+
508
+ handleLastPage = (params = {}, options = {}) => {
509
+ const { count, limit, page } = params
510
+ const { hardCap } = this.getDefaultValues(options)
511
+ if (count > hardCap) {
512
+ const lastPage = Math.floor(hardCap / limit)
513
+ if (page > lastPage) {
514
+ const warnings = [this.t('hardCapWarning%s%s', count, hardCap)]
515
+ return { data: [], count: hardCap, warnings }
516
+ }
517
+ }
518
+ }
496
519
  }
497
520
 
498
521
  return Dobo
@@ -135,8 +135,7 @@ async function driverFactory () {
135
135
  if (isSet(body[field])) query[field] = body[field]
136
136
  }
137
137
  if (isEmpty(query)) continue
138
- const resp = await this.findRecord(model, { query, limit: 1 }, options)
139
- const data = resp.data[0]
138
+ const data = await this.findOneRecord(model, { query }, options)
140
139
  if (!isEmpty(data)) {
141
140
  if (['updateRecord', 'upsertRecord'].includes(options.action)) {
142
141
  let eq = true
@@ -383,20 +383,21 @@ export function replaceIdInQuerySearch (filter) {
383
383
  */
384
384
  export function preparePagination (filter = {}, options = {}) {
385
385
  const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.app.lib._
386
- const config = this.app.dobo.config
386
+ const { getDefaultValues, config } = this.app.dobo
387
+ const { limit: defLimit, maxLimit: defMaxLimit, maxPage: defMaxPage } = getDefaultValues(options)
387
388
 
388
389
  const buildPageSkipLimit = (filter) => {
389
- let limit = parseInt(filter.limit) || config.default.filter.limit
390
- if (limit === -1) limit = config.default.filter.maxLimit
391
- if (limit > config.default.filter.maxLimit) {
390
+ let limit = parseInt(filter.limit) || defLimit
391
+ if (limit === -1) limit = defMaxLimit
392
+ if (limit > defMaxLimit) {
392
393
  options.warnings = options.warnings ?? []
393
- options.warnings.push(options.req ? options.req.t('maxLimitWarning%s%s', limit, config.default.filter.maxLimit) : this.plugin.t('maxLimitWarning%s', limit, config.default.filter.maxLimit))
394
- limit = config.default.filter.maxLimit // TODO: notify as warning in response object
394
+ options.warnings.push(options.req ? options.req.t('maxLimitWarning%s%s', limit, defMaxLimit) : this.plugin.t('maxLimitWarning%s', limit, defMaxLimit))
395
+ limit = defMaxLimit // TODO: notify as warning in response object
395
396
  }
396
397
  if (limit < 1) limit = 1
397
398
  let page = parseInt(filter.page) || 1
398
399
  if (page < 1) page = 1
399
- if (page > config.default.filter.maxPage) throw this.plugin.error('maxPageError%s%s', page, config.default.filter.maxPage)
400
+ if (page > defMaxPage) throw this.plugin.error('maxPageError%s%s', page, defMaxPage)
400
401
  let skip = (page - 1) * limit
401
402
  if (filter.skip) {
402
403
  skip = parseInt(filter.skip) || skip
@@ -2,14 +2,22 @@ import { getFilterAndOptions, execHook, execModelHook, execDynHook } from './_ut
2
2
  const action = 'countRecord'
3
3
 
4
4
  async function countRecord (...args) {
5
+ const { getDefaultValues } = this.app.dobo
5
6
  if (args.length === 0) return this.action(action, ...args)
6
7
  const [params = {}, opts = {}] = args
7
8
  const { dataOnly = true } = opts
8
9
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
10
+ const { hardCap, t } = getDefaultValues(options)
9
11
  await execHook.call(this, 'beforeCountRecord', options)
10
12
  await execModelHook.call(this, 'beforeCountRecord', filter, options)
11
13
  await execDynHook.call(this, 'beforeCountRecord', filter, options)
12
14
  const result = (await this.driver._countRecord(this, filter, options)) ?? {}
15
+ if (result.data > hardCap) {
16
+ result.warnings = result.warnings ?? []
17
+ result.warnings.push(t('hardCapWarning%s%s', result.data, hardCap))
18
+ result.orgCount = result.data
19
+ result.data = hardCap
20
+ }
13
21
  await execDynHook.call(this, 'afterCountRecord', filter, result, options)
14
22
  await execModelHook.call(this, 'afterCountRecord', filter, result, options)
15
23
  await execHook.call(this, 'afterCountRecord', filter, result, options)
@@ -44,10 +44,11 @@ async function native (...args) {
44
44
 
45
45
  async function loop (...args) {
46
46
  const { cloneDeep } = this.app.lib._
47
+ const { getDefaultValues } = this.app.dobo
47
48
  const [params = {}, opts = {}] = args
48
49
  const { dataOnly = true } = opts
49
50
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
50
- const { maxLimit, hardLimit } = this.app.dobo.config.default.filter
51
+ const { maxLimit, hardCap } = getDefaultValues(options)
51
52
  const nFilter = cloneDeep(filter || {})
52
53
  const nOptions = cloneOptions.call(this, options)
53
54
  nOptions.count = false
@@ -60,9 +61,9 @@ async function loop (...args) {
60
61
  for (;;) {
61
62
  const result = await this.findRecord(nFilter, nOptions)
62
63
  if (result.data.length === 0) break
63
- if (count + result.data.length > hardLimit) {
64
- const sliced = result.data.slice(0, hardLimit - count)
65
- warnings.push(options.req ? options.req.t('hardLimitWarning%s%s', result.data.length, hardLimit) : this.plugin.t('hardLimitWarning%s%s', result.data.length, hardLimit))
64
+ if (count + result.data.length > hardCap) {
65
+ const sliced = result.data.slice(0, hardCap - count)
66
+ warnings.push(options.req ? options.req.t('hardCapWarning%s%s', result.data.length, hardCap) : this.plugin.t('hardCapWarning%s%s', result.data.length, hardCap))
66
67
  data.push(...sliced)
67
68
  break
68
69
  }
@@ -66,12 +66,14 @@ const action = 'findRecord'
66
66
  */
67
67
  async function findRecord (...args) {
68
68
  if (args.length === 0) return this.action(action, ...args)
69
+ const { getDefaultValues, t } = this.app.dobo
69
70
  const [params = {}, opts = {}] = args
70
71
  const { isSet } = this.app.lib.aneka
71
- const { cloneDeep } = this.app.lib._
72
+ const { cloneDeep, pick, omit } = this.app.lib._
72
73
  const { dataOnly = true } = opts
73
74
  const { get: getCache, set: setCache } = this.app.bajoCache ?? {}
74
75
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
76
+ const { hardCap } = getDefaultValues(options)
75
77
  if (dataOnly) options.count = false
76
78
  let { noResultSanitizer, noCache } = options
77
79
  await execHook.call(this, 'beforeFindRecord', filter, options)
@@ -89,7 +91,18 @@ async function findRecord (...args) {
89
91
  return dataOnly ? result.data : result
90
92
  }
91
93
  }
92
- const result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
94
+ let result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
95
+ result.page = filter.page
96
+ result.limit = filter.limit
97
+ result.filter = pick(filter, ['query', 'match', 'sort'])
98
+ result.warnings = result.warnings ?? []
99
+ if (!options.count) result = omit(result, ['count', 'pages'])
100
+ else if (options.count && result.count > hardCap) {
101
+ result.warnings.push(t('hardCapWarning%s%s', result.count, hardCap))
102
+ result.count = hardCap
103
+ }
104
+ result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
105
+
93
106
  if (!noResultSanitizer) {
94
107
  for (const idx in result.data) {
95
108
  result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
@@ -238,7 +238,7 @@ async function validate (body, joiModel, opts = {}) {
238
238
  try {
239
239
  return await joiModel.validateAsync(body, params)
240
240
  } catch (err) {
241
- const payload = { details: err.details, statusCode: 422, code: 'DB_VALIDATION' }
241
+ const payload = { details: err.details, statusCode: 422, code: 'DB_VALIDATION', model: this.name }
242
242
  if (err.values) payload.values = err.values
243
243
  throw this.plugin.error('validationError', payload)
244
244
  }
@@ -109,7 +109,9 @@ async function modelFactory () {
109
109
  if (isEmpty(field)) field = 'id'
110
110
  const { getModel } = this.app.dobo
111
111
  const ref = getModel(model)
112
- const rec = await ref.findOneRecord({ query }, { noHook: true, noCache: true, ...options })
112
+ const opts = { noHook: true, noCache: true, ...options }
113
+ opts.dataOnly = true
114
+ const rec = await ref.findOneRecord({ query }, opts)
113
115
  return get(rec, field, null)
114
116
  }
115
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.11.4",
3
+ "version": "2.13.0",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-03-30
4
+
5
+ - [2.12.0] Add ```.bootorder``` level 10
6
+ - [2.12.0] Bug fix in ```model._simpleLookup()```
7
+ - [2.12.0] Add model name in validation error's payload
8
+ - [2.13.0] Add ```hardCap``` support
9
+
3
10
  ## 2026-03-26
4
11
 
5
12
  - [2.11.4] Exceptions thrown in ```getSingleRef()``` && ```getMultiRefs()``` will be catched and are ignored
package/wiki/CONFIG.md CHANGED
@@ -16,7 +16,7 @@
16
16
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```filter``` | ```object``` | | |
17
17
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```limit``` | ```number``` | ```25``` | Rows returned in one page |
18
18
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```maxLimit``` | ```number``` | ```200``` | Max rows returned in one page |
19
- | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```hardLimit``` | ```number``` | ```10000``` | Max rows returned on dataset export |
19
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```hardCap``` | ```number``` | ```10000``` | Max rows returned on dataset export |
20
20
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```sort``` | ```array``` | | |
21
21
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;```idField``` | ```object``` | | |
22
22
  | ```memDb``` | ```object``` | | |