dobo 2.28.0 → 2.29.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.
@@ -3,7 +3,20 @@ async function beforeRemoveRecord (id, opts, options) {
3
3
  if (get(options, 'req.user.isXSiteAdmin')) return
4
4
  const record = await this.driver.getRecord(this, id)
5
5
  const immutable = get(record.data, opts.field)
6
- if (immutable) throw this.plugin.error('recordImmutable%s%s', id, this.name, { statusCode: 423 })
6
+ if (immutable.length === 1 && immutable[0] === '*') throw this.plugin.error('recordImmutable%s%s', id, this.name, { statusCode: 423 })
7
+ }
8
+
9
+ async function beforeUpdateRecord (id, body, opts, options) {
10
+ const { get } = this.app.lib._
11
+ if (get(options, 'req.user.isXSiteAdmin')) return
12
+ const record = await this.driver.getRecord(this, id)
13
+ const immutable = get(record.data, opts.field)
14
+ if (immutable.length === 0) return
15
+ const fields = []
16
+ if (immutable.length === 1 && immutable[0] === '*') fields.push(...this.getNonVirtualProperties(true))
17
+ for (const field of fields) {
18
+ delete body[field]
19
+ }
7
20
  }
8
21
 
9
22
  async function immutable (opts = {}) {
@@ -11,12 +24,13 @@ async function immutable (opts = {}) {
11
24
  return {
12
25
  properties: {
13
26
  name: opts.field,
14
- type: 'boolean'
27
+ type: 'array',
28
+ default: []
15
29
  },
16
30
  hooks: [{
17
31
  name: 'beforeUpdateRecord',
18
32
  handler: async function (id, body, options) {
19
- await beforeRemoveRecord.call(this, id, opts, options)
33
+ await beforeUpdateRecord.call(this, id, body, opts, options)
20
34
  }
21
35
  }, {
22
36
  name: 'beforeRemoveRecord',
@@ -72,7 +72,6 @@ export async function getFilterAndOptions (filter = {}, options = {}, action) {
72
72
  const nFilter = cloneDeep(filter || {})
73
73
  const nOptions = cloneOptions.call(this, options)
74
74
  if (options.noMagic) {
75
- nOptions.noModelHook = true
76
75
  nOptions.noHook = true
77
76
  nOptions.noDynHook = true
78
77
  nOptions.noValidation = true
@@ -1,14 +1,14 @@
1
1
  import path from 'path'
2
2
 
3
- async function exec ({ item, spinner, options, result, items } = {}) {
3
+ async function exec ({ body, spinner, options, result, bodies } = {}) {
4
4
  const { isArray, isString } = this.app.lib._
5
5
  const { getPluginDataDir } = this.app.bajo
6
6
  const { fs } = this.app.lib
7
7
 
8
8
  await this.transaction(async (trx) => {
9
- const resp = await this.createRecord(item, { ...options, trx })
10
- if (isArray(item._attachments) && item._attachments.length > 0) {
11
- for (let att of item._attachments) {
9
+ const resp = await this.createRecord(body, { ...options, trx })
10
+ if (isArray(body._attachments) && body._attachments.length > 0) {
11
+ for (let att of body._attachments) {
12
12
  if (isString(att)) att = { field: 'file', file: att }
13
13
  const fname = path.basename(att.file)
14
14
  if (fs.existsSync(att.file)) {
@@ -21,19 +21,18 @@ async function exec ({ item, spinner, options, result, items } = {}) {
21
21
  }
22
22
  })
23
23
  result.success++
24
- if (spinner) spinner.setText('recordsAdded%s%d%d', this.name, result.success, items.length)
24
+ if (spinner) spinner.setText('recordsAdded%s%d%d', this.name, result.success, bodies.length)
25
25
  }
26
26
 
27
27
  async function loadFixtures ({ spinner, ignoreError = true, collectItems = false, noLookup = false } = {}, options = {}) {
28
28
  const { readConfig } = this.app.bajo
29
- const { isSet } = this.app.lib.aneka
30
- const { isEmpty, isString, isArray, pullAt } = this.app.lib._
29
+ const { isEmpty } = this.app.lib._
31
30
  if (this.connection.proxy) {
32
31
  this.log.warn('proxiedConnBound%s', this.name)
33
32
  return
34
33
  }
35
34
  const result = { success: 0, failed: 0 }
36
- const items = await readConfig(`${this.plugin.ns}:/extend/dobo/fixture/${this.baseName}.*`, { ns: this.plugin.ns, baseNs: 'dobo', checkOverride: true, defValue: [] })
35
+ const bodies = await readConfig(`${this.plugin.ns}:/extend/dobo/fixture/${this.baseName}.*`, { ns: this.plugin.ns, baseNs: 'dobo', checkOverride: true, defValue: [] })
37
36
  const opts = {
38
37
  ...options,
39
38
  noModelHook: false,
@@ -42,49 +41,22 @@ async function loadFixtures ({ spinner, ignoreError = true, collectItems = false
42
41
  noValidation: false,
43
42
  noCache: true
44
43
  }
45
- for (const item of items) {
46
- const lv = {}
47
- const deleted = {}
48
- for (const key in item) {
49
- const val = item[key]
50
- deleted[key] = deleted[key] ?? []
51
- if (!noLookup) {
52
- if (isString(val) && val.slice(0, 2) === '?:') {
53
- item[key] = await this._simpleLookup(val.slice(2), lv, opts)
54
- lv[key] = item[key]
55
- } else if (isArray(val)) {
56
- for (const idx in val) {
57
- if (isString(val[idx]) && val[idx].slice(0, 2) === '?:') {
58
- item[key][idx] = await this._simpleLookup(val[idx].slice(2), lv, opts)
59
- if (isSet(item[key][idx])) item[key][idx] += ''
60
- else deleted[key].push(idx)
61
- lv[`${key}.${idx}`] = item[key][idx]
62
- }
63
- }
64
- if (deleted[key].length > 0) pullAt(item[key], deleted[key])
65
- }
66
- }
67
- delete deleted[key]
68
- if (val === null) item[key] = undefined
69
- else {
70
- const prop = this.properties.find(item => item.name === key)
71
- if (prop && ['string', 'text'].includes(prop.type)) item[key] += ''
72
- }
73
- }
44
+ for (const body of bodies) {
45
+ await this.sanitizeFixture({ body, noLookup }, options)
74
46
  }
75
- if (collectItems) return items
76
- if (isEmpty(items)) return result
77
- for (const item of items) {
47
+ if (collectItems) return bodies
48
+ if (isEmpty(bodies)) return result
49
+ for (const body of bodies) {
78
50
  if (ignoreError) {
79
51
  try {
80
- await exec.call(this, { item, spinner, options: opts, result, items })
52
+ await exec.call(this, { body, spinner, options: opts, result, bodies })
81
53
  } catch (err) {
82
54
  if (this.app.bajo.config.log.applet) console.error(err)
83
55
  err.model = this.name
84
56
  if (this.app.applet) this.plugin.print.fail(this.app.dobo.validationErrorMessage(err))
85
57
  result.failed++
86
58
  }
87
- } else await exec.call(this, { item, spinner, options: opts, result, items })
59
+ } else await exec.call(this, { body, spinner, options: opts, result, bodies })
88
60
  }
89
61
  return result
90
62
  }
@@ -33,11 +33,11 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
33
33
 
34
34
  const omitted = []
35
35
  const details = []
36
- const properties = [...this.properties, ...extFields]
36
+ const properties = [...this.getNonVirtualProperties(), ...extFields]
37
37
  for (const prop of properties) {
38
38
  try {
39
39
  if (partial && !has(body, prop.name)) {
40
- if (prop.type === 'array') result[prop.name] = null
40
+ // if (prop.type === 'array') result[prop.name] = null
41
41
  continue
42
42
  }
43
43
  result[prop.name] = body[prop.name]
@@ -0,0 +1,43 @@
1
+ async function sanitizeFixture ({ body = {}, lookupValue = {}, noLookup } = {}, options = {}) {
2
+ const { isString, isArray, pullAt, cloneDeep } = this.app.lib._
3
+ const { isSet } = this.app.lib.aneka
4
+ const lv = cloneDeep(lookupValue)
5
+ const deleted = {}
6
+ const opts = {
7
+ ...options,
8
+ noModelHook: false,
9
+ noHook: true,
10
+ noDynHook: true,
11
+ noValidation: false,
12
+ noCache: true
13
+ }
14
+ for (const key in body) {
15
+ const val = body[key]
16
+ deleted[key] = deleted[key] ?? []
17
+ if (!noLookup) {
18
+ if (isString(val) && val.slice(0, 2) === '?:') {
19
+ body[key] = await this._simpleLookup(val.slice(2), lv, opts)
20
+ lv[key] = body[key]
21
+ } else if (isArray(val)) {
22
+ for (const idx in val) {
23
+ if (isString(val[idx]) && val[idx].slice(0, 2) === '?:') {
24
+ body[key][idx] = await this._simpleLookup(val[idx].slice(2), lv, opts)
25
+ if (isSet(body[key][idx])) body[key][idx] += ''
26
+ else deleted[key].push(idx)
27
+ lv[`${key}.${idx}`] = body[key][idx]
28
+ }
29
+ }
30
+ if (deleted[key].length > 0) pullAt(body[key], deleted[key])
31
+ }
32
+ }
33
+ delete deleted[key]
34
+ if (val === null) body[key] = undefined
35
+ else {
36
+ const prop = this.properties.find(item => item.name === key)
37
+ if (prop && ['string', 'text'].includes(prop.type)) body[key] += ''
38
+ }
39
+ }
40
+ return body
41
+ }
42
+
43
+ export default sanitizeFixture
@@ -86,7 +86,8 @@ const validator = {
86
86
  'sign', 'unsafe'],
87
87
  boolean: ['falsy', 'sensitive', 'truthy'],
88
88
  date: ['greater', 'iso', 'less', 'max', 'min'],
89
- timestamp: ['timestamp']
89
+ timestamp: ['timestamp'],
90
+ array: ['length', 'max', 'min']
90
91
  }
91
92
 
92
93
  async function buildFromDbModel (opts = {}) {
@@ -22,6 +22,7 @@ import findAttachment from './model/find-attachment.js'
22
22
  import sanitizeBody from './model/sanitize-body.js'
23
23
  import sanitizeRecord from './model/sanitize-record.js'
24
24
  import sanitizeId from './model/sanitize-id.js'
25
+ import sanitizeFixture from './model/sanitize-fixture.js'
25
26
  import upsertRecord from './model/upsert-record.js'
26
27
  import bulkCreateRecord from './model/bulk-create-record.js'
27
28
  import listAttachment from './model/list-attachment.js'
@@ -193,6 +194,7 @@ async function modelFactory () {
193
194
  sanitizeRecord = sanitizeRecord
194
195
  sanitizeBody = sanitizeBody
195
196
  sanitizeId = sanitizeId
197
+ sanitizeFixture = sanitizeFixture
196
198
  validate = validate
197
199
 
198
200
  // aliases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.28.0",
3
+ "version": "2.29.1",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-06-11
4
+
5
+ - [2.29.1] Bug fix in ```model.sanitizeBody()```
6
+
7
+ ## 2026-06-10
8
+
9
+ - [2.29.0] Feature ```dobo:immutable``` now using array instead of boolean
10
+ - [2.29.0] Add ```model.sanitizeFixture()```
11
+ - [2.29.0] Options ```noMagic``` no without touching ```noModelHook```
12
+ - [2.29.0] Add ```array``` validator type
13
+
3
14
  ## 2026-06-03
4
15
 
5
16
  - [2.28.0] Property ```values``` can now accept a function that will be called dynamically upon used