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 +1 -0
- package/extend/bajo/intl/en-US.json +1 -1
- package/extend/bajo/intl/id.json +2 -2
- package/index.js +27 -4
- package/lib/factory/driver.js +1 -2
- package/lib/factory/model/_util.js +8 -7
- package/lib/factory/model/count-record.js +8 -0
- package/lib/factory/model/find-all-record.js +5 -4
- package/lib/factory/model/find-record.js +15 -2
- package/lib/factory/model/validate.js +1 -1
- package/lib/factory/model.js +3 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +7 -0
- package/wiki/CONFIG.md +1 -1
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
|
-
"
|
|
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",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
|
|
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
|
package/lib/factory/driver.js
CHANGED
|
@@ -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
|
|
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
|
|
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) ||
|
|
390
|
-
if (limit === -1) limit =
|
|
391
|
-
if (limit >
|
|
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,
|
|
394
|
-
limit =
|
|
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 >
|
|
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,
|
|
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 >
|
|
64
|
-
const sliced = result.data.slice(0,
|
|
65
|
-
warnings.push(options.req ? options.req.t('
|
|
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
|
-
|
|
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
|
}
|
package/lib/factory/model.js
CHANGED
|
@@ -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
|
|
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
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
|
| ```filter``` | ```object``` | | |
|
|
17
17
|
| ```limit``` | ```number``` | ```25``` | Rows returned in one page |
|
|
18
18
|
| ```maxLimit``` | ```number``` | ```200``` | Max rows returned in one page |
|
|
19
|
-
| ```
|
|
19
|
+
| ```hardCap``` | ```number``` | ```10000``` | Max rows returned on dataset export |
|
|
20
20
|
| ```sort``` | ```array``` | | |
|
|
21
21
|
| ```idField``` | ```object``` | | |
|
|
22
22
|
| ```memDb``` | ```object``` | | |
|