dobo 2.17.0 → 2.18.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.
@@ -149,6 +149,7 @@
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
  "duplicateRefKeys%s%s": "Duplicate reference keys found in '%s' (%s)",
152
+ "sanitizeBodyError": "Error sanitizing body",
152
153
  "field": {
153
154
  "id": "ID",
154
155
  "code": "Kode",
@@ -147,6 +147,7 @@
147
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
  "duplicateRefKeys%s%s": "Ditemukan kunci referensi duplikat di '%s' (%s)",
150
+ "sanitizeBodyError": "Kesalahan saat sanitasi body",
150
151
  "field": {
151
152
  "id": "ID",
152
153
  "code": "Kode",
@@ -1,12 +1,30 @@
1
1
  async function dt (opts = {}) {
2
2
  opts.field = opts.field ?? 'dt'
3
+ opts.type = opts.type ??'datetime'
4
+ opts.formatInt = opts.formatInt ?? false
5
+ opts.formatValueInt = opts.formatValueInt ?? false
6
+ const prop = {
7
+ name: opts.field ?? 'dt',
8
+ type: opts.type,
9
+ required: opts.required ?? true,
10
+ index: opts.index ?? true
11
+ }
12
+ if (opts.type === 'integer') {
13
+ if (opts.formatInt) {
14
+ prop.format = async function (val, data, { req } = {}) {
15
+ const dt = new Date(data._orig[opts.field])
16
+ return req ? req.format(dt, 'datetime') : this.app.bajo.format(dt, 'datetime', { lang: req.lang })
17
+ }
18
+ }
19
+ if (opts.formatValueInt) {
20
+ prop.format = async function (val, data, { req } = {}) {
21
+ return new Date(data._orig[opts.field])
22
+ }
23
+ }
24
+ }
25
+
3
26
  return {
4
- properties: [{
5
- name: opts.field ?? 'dt',
6
- type: 'datetime',
7
- required: opts.required ?? true,
8
- index: opts.index ?? true
9
- }]
27
+ properties: [prop]
10
28
  }
11
29
  }
12
30
 
package/index.js CHANGED
@@ -390,7 +390,7 @@ async function factory (pkgName) {
390
390
  return parseInt(value) || null
391
391
  }
392
392
 
393
- sanitizeObject = (value, { strict = false } = {}) => {
393
+ sanitizeObject = (value, { strict = false, action, model } = {}) => {
394
394
  const { isString } = this.app.lib._
395
395
  let result = null
396
396
  if (isString(value)) {
@@ -1,6 +1,6 @@
1
1
  import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
2
2
 
3
- export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
3
+ export const onlyTypes = ['datetime', 'date', 'time', 'timestamp', 'array', 'object']
4
4
  const action = 'createRecord'
5
5
 
6
6
  async function createRecord (...args) {
@@ -14,7 +14,7 @@ async function createRecord (...args) {
14
14
  const { options } = await getFilterAndOptions.call(this, null, opts, action)
15
15
  const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
16
16
  const extFields = get(options, 'validation.extFields', [])
17
- const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes })
17
+ const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes, action })
18
18
  await execHook.call(this, 'beforeCreateRecord', input, options)
19
19
  await execModelHook.call(this, 'beforeCreateRecord', input, options)
20
20
  await execDynHook.call(this, 'beforeCreateRecord', input, options)
@@ -21,7 +21,7 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
21
21
 
22
22
  const sanitize = (name, type) => {
23
23
  if (onlyTypes.length > 0 && !onlyTypes.includes(type)) return
24
- if (['object', 'array'].includes(type)) result[name] = sanitizeObject(result[name], { strict })
24
+ if (['object', 'array'].includes(type)) result[name] = sanitizeObject(result[name], { strict, action, model: this.name })
25
25
  else if (type === 'boolean') result[name] = sanitizeBoolean(result[name])
26
26
  else if (['float', 'double'].includes(type)) result[name] = sanitizeFloat(result[name], { strict })
27
27
  else if (['integer', 'smallint'].includes(type)) result[name] = sanitizeInt(result[name], { strict })
@@ -32,23 +32,32 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
32
32
  }
33
33
 
34
34
  const omitted = []
35
+ const details = []
35
36
  for (const prop of [...this.properties, ...extFields]) {
36
- if (partial && !has(body, prop.name)) continue
37
- result[prop.name] = body[prop.name]
38
- if (body[prop.name] === null) continue
39
- if (isSet(body[prop.name])) sanitize(prop.name, prop.type)
40
- else {
41
- if (isSet(prop.default) && !noDefault) {
42
- result[prop.name] = prop.default
43
- if (isString(prop.default) && prop.default.startsWith('handler:')) {
44
- const [, ...args] = prop.default.split(':')
45
- if (args.length > 0) result[prop.name] = await callHandler(args.join(':'))
46
- } else sanitize(prop.name, prop.type)
37
+ try {
38
+ if (partial && !has(body, prop.name)) continue
39
+ result[prop.name] = body[prop.name]
40
+ if (body[prop.name] === null) continue
41
+ if (isSet(body[prop.name])) sanitize(prop.name, prop.type)
42
+ else {
43
+ if (isSet(prop.default) && !noDefault) {
44
+ result[prop.name] = prop.default
45
+ if (isString(prop.default) && prop.default.startsWith('handler:')) {
46
+ const [, ...args] = prop.default.split(':')
47
+ if (args.length > 0) result[prop.name] = await callHandler(args.join(':'))
48
+ } else sanitize(prop.name, prop.type)
49
+ }
47
50
  }
51
+ if (truncateString && isSet(result[prop.name]) && ['string', 'text'].includes(prop.type)) result[prop.name] = result[prop.name].slice(0, prop.maxLength)
52
+ if (prop.name.endsWith('Id') && prop.type === 'string' && ['smallint', 'integer'].includes(this.driver.idField.type)) result[prop.name] = result[prop.name] + ''
53
+ if (body[prop.name] === undefined) omitted.push(prop.name)
54
+ } catch (err) {
55
+ details.push({ field: prop.name, error: err.message, value: body[prop.name], ext: { type: prop.type } })
48
56
  }
49
- if (truncateString && isSet(result[prop.name]) && ['string', 'text'].includes(prop.type)) result[prop.name] = result[prop.name].slice(0, prop.maxLength)
50
- if (prop.name.endsWith('Id') && prop.type === 'string' && ['smallint', 'integer'].includes(this.driver.idField.type)) result[prop.name] = result[prop.name] + ''
51
- if (body[prop.name] === undefined) omitted.push(prop.name)
57
+ }
58
+ if (details.length > 0) {
59
+ const payload = { details, statusCode: 422, code: 'BODY_SANITIZATION', model: this.name }
60
+ throw this.plugin.error('sanitizeBodyError', payload)
52
61
  }
53
62
  return omit(result, omitted)
54
63
  }
@@ -29,6 +29,11 @@ async function sanitizeRecord (record = {}, opts = {}) {
29
29
  const prop = this.getProperty(key)
30
30
  if (!prop) continue
31
31
  const value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
32
+ if (prop.formatValue && opts.retainOriginalValue) {
33
+ const value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord._orig[key]) : newRecord._orig[key]
34
+ if (isFunction(prop.formatValue)) newRecord._orig[key] = await prop.formatValue.call(this, value, newRecord._orig, opts)
35
+ else if (isString(prop.formatValue)) newRecord._orig[key] = await callHandler(this.plugin, this, value, newRecord._orig, opts)
36
+ }
32
37
  if (prop.format) {
33
38
  if (isFunction(prop.format)) newRecord[key] = await prop.format.call(this, value, newRecord, opts)
34
39
  else if (isString(prop.format)) newRecord[key] = await callHandler(this.plugin, this, value, newRecord, opts)
@@ -58,7 +58,7 @@ async function updateRecord (...args) {
58
58
  const extFields = get(options, 'validation.extFields', [])
59
59
  id = this.sanitizeId(id)
60
60
 
61
- let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, partial, onlyTypes })
61
+ let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, partial, onlyTypes, action })
62
62
  const immutables = this.properties.filter(prop => prop.immutable)
63
63
  if (immutables.length > 0) input = omit(input, immutables)
64
64
  delete input.id
@@ -10,7 +10,7 @@ async function native (body = {}, opts = {}) {
10
10
  const { options } = await getFilterAndOptions.call(this, null, opts, action)
11
11
  const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
12
12
  const extFields = get(options, 'validation.extFields', [])
13
- let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString })
13
+ let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, action })
14
14
  if (!noValidation) input = await execValidation.call(this, input, options)
15
15
  await execHook.call(this, 'beforeUpsertRecord', input, options)
16
16
  await execModelHook.call(this, 'beforeUpsertRecord', input, options)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-04-16
4
+
5
+ - [2.18.0] Add parameter options to ```sanitizeObject()```
6
+ - [2.18.0] Rewrite ```dobo:dt``` feature to support customization
7
+ - [2.18.0] Bug fix in ```model.createRecord()```
8
+ - [2.18.0] Changes in ```model.sanitizeBody()``` to throw error with payload details
9
+ - [2.18.0] Changes in ```model.sanitizeRecord()``` to support ```options.retainOriginalValue```
10
+ - [2.18.0] Bug fix in ```model.updateRecord()```
11
+ - [2.18.0] Bug fix in ```model.upsertRecord()```
12
+
3
13
  ## 2026-04-13
4
14
 
5
15
  - [2.17.0] Add ```{ strict }``` as parameter to ```sanitizeObject()```