dobo 2.23.0 → 2.25.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/extend/bajo/intl/en-US.json +3 -0
- package/extend/bajo/intl/id.json +3 -0
- package/extend/dobo/feature/immutable.js +2 -3
- package/index.js +2 -2
- package/lib/factory/driver.js +97 -7
- package/lib/factory/model/_util.js +4 -10
- package/lib/factory/model/load-fixtures.js +27 -8
- package/lib/factory/model/sanitize-body.js +2 -2
- package/lib/factory/model/transaction.js +10 -1
- package/lib/factory/model/update-record.js +1 -1
- package/lib/factory/model/validate.js +7 -2
- package/lib/factory/model.js +11 -3
- package/package.json +1 -1
- package/wiki/CHANGES.md +20 -0
package/extend/bajo/intl/id.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
async function beforeRemoveRecord (id, opts, options) {
|
|
2
2
|
const { get } = this.app.lib._
|
|
3
|
-
if (get(options, 'req.user.
|
|
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
6
|
if (immutable) throw this.plugin.error('recordImmutable%s%s', id, this.name, { statusCode: 423 })
|
|
@@ -11,8 +11,7 @@ async function immutable (opts = {}) {
|
|
|
11
11
|
return {
|
|
12
12
|
properties: {
|
|
13
13
|
name: opts.field,
|
|
14
|
-
type: 'boolean'
|
|
15
|
-
hidden: true
|
|
14
|
+
type: 'boolean'
|
|
16
15
|
},
|
|
17
16
|
hooks: [{
|
|
18
17
|
name: 'beforeUpdateRecord',
|
package/index.js
CHANGED
package/lib/factory/driver.js
CHANGED
|
@@ -12,8 +12,9 @@ const defIdField = {
|
|
|
12
12
|
|
|
13
13
|
async function driverFactory () {
|
|
14
14
|
const { Tools } = this.app.baseClass
|
|
15
|
-
const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction } = this.app.lib._
|
|
15
|
+
const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction, camelCase, last } = this.app.lib._
|
|
16
16
|
const { isSet } = this.app.lib.aneka
|
|
17
|
+
const { runHook } = this.app.bajo
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Driver class
|
|
@@ -111,6 +112,7 @@ async function driverFactory () {
|
|
|
111
112
|
const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
|
|
112
113
|
item[prop.name] = dt.toDate()
|
|
113
114
|
}
|
|
115
|
+
if (prop.type === 'boolean' && isSet(item[prop.name])) item[prop.name] = Boolean(item[prop.name])
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
return item
|
|
@@ -124,6 +126,15 @@ async function driverFactory () {
|
|
|
124
126
|
return uniq(items)
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
async _attachHook (name, model, ...args) {
|
|
130
|
+
const { ns } = this.app.dobo
|
|
131
|
+
const options = last(args)
|
|
132
|
+
if (!options.noDriverHook) {
|
|
133
|
+
await runHook(`${ns}:${name}`, model, ...args)
|
|
134
|
+
await runHook(`${ns}.${camelCase(model.name)}:${name}`, model, ...args)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
/**
|
|
128
139
|
* Check uniqueness of fields with unique index
|
|
129
140
|
*
|
|
@@ -245,7 +256,11 @@ async function driverFactory () {
|
|
|
245
256
|
if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
|
|
246
257
|
}
|
|
247
258
|
body = this.sanitizeBody(model, body)
|
|
259
|
+
|
|
260
|
+
await this._attachHook('beforeDriverCreateRecord', model, body, options)
|
|
248
261
|
const result = await this.createRecord(model, body, options)
|
|
262
|
+
await this._attachHook('afterDriverCreateRecord', model, body, result, options)
|
|
263
|
+
|
|
249
264
|
if (options.noResult) return
|
|
250
265
|
result.data = this.sanitizeRecord(model, result.data)
|
|
251
266
|
this._injectMeta(result, options)
|
|
@@ -261,14 +276,20 @@ async function driverFactory () {
|
|
|
261
276
|
await this._prepIdForCreate(model, body, options)
|
|
262
277
|
bodies[idx] = this.sanitizeBody(model, body)
|
|
263
278
|
}
|
|
279
|
+
|
|
280
|
+
await this._attachHook('beforeDriverBulkCreateRecord', model, bodies, options)
|
|
264
281
|
const items = chunk(bodies, chunkSize)
|
|
265
282
|
for (const item of items) {
|
|
266
283
|
await this.bulkCreateRecord(model, item, options)
|
|
267
284
|
}
|
|
285
|
+
await this._attachHook('afterDriverBulkCreateRecord', model, bodies, [], options)
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
async _getRecord (model, id, options = {}) {
|
|
289
|
+
await this._attachHook('beforeDriverGetRecord', model, id, options)
|
|
271
290
|
const result = await this.getRecord(model, id, options)
|
|
291
|
+
await this._attachHook('afterDriverGetRecord', model, id, result, options)
|
|
292
|
+
|
|
272
293
|
if (isEmpty(result.data) && options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
273
294
|
result.data = this.sanitizeRecord(model, result.data)
|
|
274
295
|
this._injectMeta(result, options)
|
|
@@ -287,7 +308,11 @@ async function driverFactory () {
|
|
|
287
308
|
}
|
|
288
309
|
body = this.sanitizeBody(model, body, true)
|
|
289
310
|
delete body.id
|
|
311
|
+
|
|
312
|
+
await this._attachHook('beforeDriverUpdateRecord', model, id, body, options)
|
|
290
313
|
const result = await this.updateRecord(model, id, body, options)
|
|
314
|
+
await this._attachHook('afterDriverUpdateRecord', model, id, body, result, options)
|
|
315
|
+
|
|
291
316
|
if (options.noResult) return
|
|
292
317
|
result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
293
318
|
result.data = this.sanitizeRecord(model, result.data)
|
|
@@ -308,7 +333,11 @@ async function driverFactory () {
|
|
|
308
333
|
}
|
|
309
334
|
}
|
|
310
335
|
body = this.sanitizeBody(model, body)
|
|
336
|
+
|
|
337
|
+
await this._attachHook('beforeDriverUpsertRecord', model, body, options)
|
|
311
338
|
const result = await this.upsertRecord(model, body, options)
|
|
339
|
+
await this._attachHook('afterDriverUpsertRecord', model, body, result, options)
|
|
340
|
+
|
|
312
341
|
if (options.noResult) return
|
|
313
342
|
if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
314
343
|
result.data = this.sanitizeRecord(model, result.data)
|
|
@@ -322,7 +351,11 @@ async function driverFactory () {
|
|
|
322
351
|
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
323
352
|
options._data = resp.data
|
|
324
353
|
}
|
|
354
|
+
|
|
355
|
+
await this._attachHook('beforeDriverRemoveRecord', model, id, options)
|
|
325
356
|
const result = await this.removeRecord(model, id, options)
|
|
357
|
+
await this._attachHook('afterDriverRemoveRecord', model, id, result, options)
|
|
358
|
+
|
|
326
359
|
if (options.noResult) return
|
|
327
360
|
result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
328
361
|
this._injectMeta(result, options)
|
|
@@ -330,13 +363,29 @@ async function driverFactory () {
|
|
|
330
363
|
}
|
|
331
364
|
|
|
332
365
|
async _clearRecord (model, options = {}) {
|
|
366
|
+
await this._attachHook('beforeDriverClearRecord', model, options)
|
|
333
367
|
const result = await this.clearRecord(model, options)
|
|
368
|
+
await this._attachHook('afterDriverClearRecord', model, result, options)
|
|
369
|
+
|
|
334
370
|
this._injectMeta(result, options)
|
|
335
371
|
return result
|
|
336
372
|
}
|
|
337
373
|
|
|
338
374
|
async _findRecord (model, filter = {}, options = {}) {
|
|
339
|
-
|
|
375
|
+
let result
|
|
376
|
+
try {
|
|
377
|
+
await this._attachHook('beforeDriverFindRecord', model, filter, options)
|
|
378
|
+
result = await this.findRecord(model, filter, options)
|
|
379
|
+
await this._attachHook('afterDriverFindRecord', model, filter, result, options)
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
382
|
+
result = {
|
|
383
|
+
data: [],
|
|
384
|
+
count: 0
|
|
385
|
+
// warnings: [] // TODO: should generate warnings?
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
340
389
|
for (const idx in result.data) {
|
|
341
390
|
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
342
391
|
}
|
|
@@ -345,7 +394,20 @@ async function driverFactory () {
|
|
|
345
394
|
}
|
|
346
395
|
|
|
347
396
|
async _findAllRecord (model, filter = {}, options = {}) {
|
|
348
|
-
|
|
397
|
+
let result
|
|
398
|
+
try {
|
|
399
|
+
await this._attachHook('beforeDriverFindAllRecord', model, filter, options)
|
|
400
|
+
result = await this.findAllRecord(model, filter, options)
|
|
401
|
+
await this._attachHook('afterDriverFindAllRecord', model, filter, result, options)
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
404
|
+
result = {
|
|
405
|
+
data: [],
|
|
406
|
+
count: 0
|
|
407
|
+
// warnings: [] // TODO: should generate warnings?
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
349
411
|
for (const idx in result.data) {
|
|
350
412
|
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
351
413
|
}
|
|
@@ -354,7 +416,17 @@ async function driverFactory () {
|
|
|
354
416
|
}
|
|
355
417
|
|
|
356
418
|
async _countRecord (model, filter = {}, options = {}) {
|
|
357
|
-
|
|
419
|
+
let result
|
|
420
|
+
try {
|
|
421
|
+
await this._attachHook('beforeDriverCountRecord', model, filter, options)
|
|
422
|
+
result = await this.countRecord(model, filter, options)
|
|
423
|
+
await this._attachHook('afterDriverCountRecord', model, filter, result, options)
|
|
424
|
+
} catch (err) {
|
|
425
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
426
|
+
result = { data: 0 }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return result
|
|
358
430
|
}
|
|
359
431
|
|
|
360
432
|
async _createAggregate (model, filter = {}, params = {}, options = {}) {
|
|
@@ -371,7 +443,16 @@ async function driverFactory () {
|
|
|
371
443
|
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
|
|
372
444
|
// if (!fieldPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', field, fieldPropTypes.join(', '))
|
|
373
445
|
|
|
374
|
-
|
|
446
|
+
let result
|
|
447
|
+
try {
|
|
448
|
+
await this._attachHook('beforeDriverCreateAggregate', model, filter, params, options)
|
|
449
|
+
result = await this.createAggregate(model, filter, params, options)
|
|
450
|
+
await this._attachHook('afterDriverCreateAggregate', model, filter, params, result, options)
|
|
451
|
+
} catch (err) {
|
|
452
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
453
|
+
result = { data: [] }
|
|
454
|
+
}
|
|
455
|
+
|
|
375
456
|
for (const idx in result.data) {
|
|
376
457
|
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
377
458
|
}
|
|
@@ -391,8 +472,17 @@ async function driverFactory () {
|
|
|
391
472
|
|
|
392
473
|
prop = model.properties.find(p => p.name === field)
|
|
393
474
|
if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
|
|
394
|
-
|
|
395
|
-
|
|
475
|
+
|
|
476
|
+
let result
|
|
477
|
+
try {
|
|
478
|
+
await this._attachHook('beforeDriverCreateHistogram', model, filter, params, options)
|
|
479
|
+
result = await this.createHistogram(model, filter, params, options)
|
|
480
|
+
await this._attachHook('afterDriverCreateHistogram', model, filter, params, result, options)
|
|
481
|
+
} catch (err) {
|
|
482
|
+
if (err.message !== '_emptyColumnQuery') throw err
|
|
483
|
+
result = { data: [] }
|
|
484
|
+
}
|
|
485
|
+
|
|
396
486
|
for (const idx in result.data) {
|
|
397
487
|
result.data[idx] = this.sanitizeRecord(model, result.data[idx])
|
|
398
488
|
}
|
|
@@ -19,9 +19,9 @@ export async function execHook (name, ...args) {
|
|
|
19
19
|
let [prefix, ...action] = kebabCase(name).split('-')
|
|
20
20
|
action = camelCase(action.join(' '))
|
|
21
21
|
if (!noHook) {
|
|
22
|
-
if (prefix === 'before') await runHook(`${ns}:beforeAction`, action, ...args)
|
|
22
|
+
if (prefix === 'before') await runHook(`${ns}:beforeAction`, action, this.name, ...args)
|
|
23
23
|
await runHook(`${ns}:${name}`, this.name, ...args)
|
|
24
|
-
if (prefix === 'after') await runHook(`${ns}:afterAction`, action, ...args)
|
|
24
|
+
if (prefix === 'after') await runHook(`${ns}:afterAction`, action, this.name, ...args)
|
|
25
25
|
if (prefix === 'before') await runHook(`${ns}.${camelCase(this.name)}:beforeAction`, action, ...args)
|
|
26
26
|
await runHook(`${ns}.${camelCase(this.name)}:${name}`, ...args)
|
|
27
27
|
if (prefix === 'after') await runHook(`${ns}.${camelCase(this.name)}:afterAction`, action, ...args)
|
|
@@ -29,7 +29,6 @@ export async function execHook (name, ...args) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export async function execModelHook (name, ...args) {
|
|
32
|
-
if (['beforeBuildQuery', 'beforeBuildSearch', 'afterBuildQuery', 'afterBuildSearch'].includes(name)) return
|
|
33
32
|
const { last } = this.app.lib._
|
|
34
33
|
const { runModelHook } = this.app.dobo
|
|
35
34
|
const { noModelHook } = last(args)
|
|
@@ -68,7 +67,6 @@ export async function execValidation (body, options = {}) {
|
|
|
68
67
|
*/
|
|
69
68
|
export async function getFilterAndOptions (filter = {}, options = {}, action) {
|
|
70
69
|
const { cloneDeep } = this.app.lib._
|
|
71
|
-
const { runModelHook } = this.app.dobo
|
|
72
70
|
const nFilter = cloneDeep(filter || {})
|
|
73
71
|
const nOptions = cloneOptions.call(this, options)
|
|
74
72
|
if (options.noMagic) {
|
|
@@ -87,12 +85,8 @@ export async function getFilterAndOptions (filter = {}, options = {}, action) {
|
|
|
87
85
|
nOptions.throwNotFound = nOptions.throwNotFound ?? true
|
|
88
86
|
nFilter.orgQuery = nFilter.query
|
|
89
87
|
nFilter.orgSearch = nFilter.search
|
|
90
|
-
if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuildQuery', nFilter.query, nOptions)
|
|
91
88
|
nFilter.query = buildFilterQuery.call(this, nFilter) ?? {}
|
|
92
|
-
if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildQuery', nFilter.query, nOptions)
|
|
93
|
-
if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuilSearch', nFilter.search, nOptions)
|
|
94
89
|
nFilter.search = buildFilterSearch.call(this, nFilter) ?? {}
|
|
95
|
-
if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildSearch', nFilter.search, nOptions)
|
|
96
90
|
const { limit, page, skip, sort } = preparePagination.call(this, nFilter, nOptions)
|
|
97
91
|
nFilter.limit = limit
|
|
98
92
|
nFilter.page = page
|
|
@@ -194,7 +188,7 @@ export async function getRefs (records = [], options = {}) {
|
|
|
194
188
|
if (!rModel) return
|
|
195
189
|
let matches = []
|
|
196
190
|
for (const rec of records) {
|
|
197
|
-
const items = isValues ? [...rec[prop.name]] : [rec[prop.name]]
|
|
191
|
+
const items = isValues ? [...(rec[prop.name] ?? [])] : (rec[prop.name] ? [rec[prop.name]] : [])
|
|
198
192
|
matches.push(...items.map(item => prop.name === 'id' ? rModel.sanitizeId(item) : item))
|
|
199
193
|
}
|
|
200
194
|
matches = uniq(without(matches, undefined, null, NaN)).map(i => i + '')
|
|
@@ -217,7 +211,7 @@ export async function getRefs (records = [], options = {}) {
|
|
|
217
211
|
for (const i in records) {
|
|
218
212
|
records[i]._ref = records[i]._ref ?? {}
|
|
219
213
|
const rec = records[i]
|
|
220
|
-
let items = isValues ? [...rec[prop.name]] : [rec[prop.name]]
|
|
214
|
+
let items = isValues ? [...(rec[prop.name] ?? [])] : (rec[prop.name] ? [rec[prop.name]] : [])
|
|
221
215
|
items = items.map(item => item + '')
|
|
222
216
|
const res = results.filter(r => items.includes(r[ref.field] + ''))
|
|
223
217
|
if (res.length === 0) records[i]._ref[key] = isValues ? [] : {}
|
|
@@ -26,8 +26,8 @@ async function exec ({ item, spinner, options, result, items } = {}) {
|
|
|
26
26
|
|
|
27
27
|
async function loadFixtures ({ spinner, ignoreError = true, collectItems = false, noLookup = false } = {}, options = {}) {
|
|
28
28
|
const { readConfig } = this.app.bajo
|
|
29
|
-
const { resolvePath } = this.app.lib.aneka
|
|
30
|
-
const { isEmpty } = this.app.lib._
|
|
29
|
+
const { resolvePath, isSet } = this.app.lib.aneka
|
|
30
|
+
const { isEmpty, isString, isArray, pullAt } = this.app.lib._
|
|
31
31
|
if (this.connection.proxy) {
|
|
32
32
|
this.log.warn('proxiedConnBound%s', this.name)
|
|
33
33
|
return
|
|
@@ -38,13 +38,32 @@ async function loadFixtures ({ spinner, ignoreError = true, collectItems = false
|
|
|
38
38
|
const items = await readConfig(pattern, { ns: this.plugin.ns, baseNs: 'dobo', checkOverride: true, defValue: [] })
|
|
39
39
|
const opts = { ...options, noMagic: true }
|
|
40
40
|
for (const item of items) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const lv = {}
|
|
42
|
+
const deleted = {}
|
|
43
|
+
for (const key in item) {
|
|
44
|
+
const val = item[key]
|
|
45
|
+
deleted[key] = deleted[key] ?? []
|
|
46
|
+
if (!noLookup) {
|
|
47
|
+
if (isString(val) && val.slice(0, 2) === '?:') {
|
|
48
|
+
item[key] = await this._simpleLookup(val.slice(2), lv, opts)
|
|
49
|
+
lv[key] = item[key]
|
|
50
|
+
} else if (isArray(val)) {
|
|
51
|
+
for (const idx in val) {
|
|
52
|
+
if (isString(val[idx]) && val[idx].slice(0, 2) === '?:') {
|
|
53
|
+
item[key][idx] = await this._simpleLookup(val[idx].slice(2), lv, opts)
|
|
54
|
+
if (isSet(item[key][idx])) item[key][idx] += ''
|
|
55
|
+
else deleted[key].push(idx)
|
|
56
|
+
lv[`${key}.${idx}`] = item[key][idx]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (deleted[key].length > 0) pullAt(item[key], deleted[key])
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
delete deleted[key]
|
|
63
|
+
if (val === null) item[key] = undefined
|
|
45
64
|
else {
|
|
46
|
-
const prop = this.properties.find(item => item.name ===
|
|
47
|
-
if (prop && ['string', 'text'].includes(prop.type)) item[
|
|
65
|
+
const prop = this.properties.find(item => item.name === key)
|
|
66
|
+
if (prop && ['string', 'text'].includes(prop.type)) item[key] += ''
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
}
|
|
@@ -41,7 +41,6 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
|
|
|
41
41
|
continue
|
|
42
42
|
}
|
|
43
43
|
result[prop.name] = body[prop.name]
|
|
44
|
-
if (result[prop.name] === null) continue
|
|
45
44
|
if (prop.type === 'array' && isSet(result[prop.name]) && !Array.isArray(result[prop.name])) result[prop.name] = [result[prop.name]]
|
|
46
45
|
if (isSet(result[prop.name])) sanitize(prop.name, prop.type)
|
|
47
46
|
else {
|
|
@@ -53,8 +52,9 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
|
|
|
53
52
|
} else sanitize(prop.name, prop.type)
|
|
54
53
|
}
|
|
55
54
|
}
|
|
55
|
+
if (result[prop.name] === null) continue
|
|
56
56
|
if (truncateString && isSet(result[prop.name]) && ['string', 'text'].includes(prop.type)) result[prop.name] = result[prop.name].slice(0, prop.maxLength)
|
|
57
|
-
if (prop.name.endsWith('Id') && prop.type === 'string' && ['smallint', 'integer'].includes(this.driver.idField.type)) result[prop.name] = result[prop.name] + ''
|
|
57
|
+
if (prop.name.endsWith('Id') && isSet(result[prop.name]) && prop.type === 'string' && ['smallint', 'integer'].includes(this.driver.idField.type)) result[prop.name] = result[prop.name] + ''
|
|
58
58
|
if (result[prop.name] === undefined) omitted.push(prop.name)
|
|
59
59
|
} catch (err) {
|
|
60
60
|
details.push({ field: prop.name, error: err.message, value: body[prop.name], ext: { type: prop.type } })
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
async function transaction (handler, ...args) {
|
|
2
2
|
if (!this.driver.support.transaction) return handler.call(this, ...args)
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const { ns } = this.app.dobo
|
|
5
|
+
const { camelCase } = this.app.lib._
|
|
6
|
+
const { runHook } = this.app.bajo
|
|
7
|
+
const name = 'afterTransaction'
|
|
8
|
+
const result = await this.driver.transaction(this, handler, ...args)
|
|
9
|
+
const [action, ...params] = args
|
|
10
|
+
await runHook(`${ns}:${name}`, this.name, action, result, ...params)
|
|
11
|
+
await runHook(`${ns}.${camelCase(this.name)}:${name}`, action, result, ...params)
|
|
12
|
+
return result
|
|
4
13
|
}
|
|
5
14
|
|
|
6
15
|
export default transaction
|
|
@@ -66,7 +66,7 @@ async function updateRecord (...args) {
|
|
|
66
66
|
await execModelHook.call(this, 'beforeUpdateRecord', id, input, options)
|
|
67
67
|
await execDynHook.call(this, 'beforeUpdateRecord', id, input, options)
|
|
68
68
|
if (!noValidation) await execValidation.call(this, input, options)
|
|
69
|
-
const result =
|
|
69
|
+
const result = await this.driver._updateRecord(this, id, input, options)
|
|
70
70
|
await handleReq.call(this, result.data.id, 'updated', options)
|
|
71
71
|
await clearCache.call(this, id)
|
|
72
72
|
if (noResult) return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import joi from 'joi'
|
|
2
2
|
|
|
3
|
-
const excludedTypes = ['object'
|
|
3
|
+
const excludedTypes = ['object']
|
|
4
4
|
const excludedNames = []
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -142,7 +142,9 @@ async function buildFromDbModel (opts = {}) {
|
|
|
142
142
|
const resp = await callHandler(prop.values)
|
|
143
143
|
items = resp.map(item => item.value)
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
if (prop.type === 'array') {
|
|
146
|
+
obj = obj.items(joi.string().valid(...items))
|
|
147
|
+
} else obj = obj.valid(...items)
|
|
146
148
|
}
|
|
147
149
|
if (prop.rulesMsg) {
|
|
148
150
|
const msgs = {}
|
|
@@ -185,6 +187,9 @@ async function buildFromDbModel (opts = {}) {
|
|
|
185
187
|
case 'boolean':
|
|
186
188
|
item = await applyFieldRules(p, joi.boolean())
|
|
187
189
|
break
|
|
190
|
+
case 'array':
|
|
191
|
+
item = await applyFieldRules(p, joi.array())
|
|
192
|
+
break
|
|
188
193
|
}
|
|
189
194
|
if (item) {
|
|
190
195
|
if (item.$_root && !p.required) obj[p.name] = item.allow(null, '')
|
package/lib/factory/model.js
CHANGED
|
@@ -96,11 +96,16 @@ async function modelFactory () {
|
|
|
96
96
|
return namesOnly ? items.map(item => item.name) : items
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
getVirtualProperties = (
|
|
99
|
+
getVirtualProperties = (namesOnly) => {
|
|
100
100
|
const items = this.properties.filter(prop => prop.virtual)
|
|
101
101
|
return namesOnly ? items.map(item => item.name) : items
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
getNonVirtualProperties = (namesOnly) => {
|
|
105
|
+
const items = this.properties.filter(prop => !prop.virtual)
|
|
106
|
+
return namesOnly ? items.map(item => item.name) : items
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
getIndexes = () => {
|
|
105
110
|
return this.indexes
|
|
106
111
|
}
|
|
@@ -119,7 +124,7 @@ async function modelFactory () {
|
|
|
119
124
|
this.properties.unshift(idField)
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
_simpleLookup = async (value, options = {}) => {
|
|
127
|
+
_simpleLookup = async (value, lookupValue, options = {}) => {
|
|
123
128
|
const { get, isEmpty, isString, isPlainObject, isArray } = this.app.lib._
|
|
124
129
|
let model
|
|
125
130
|
let field
|
|
@@ -130,10 +135,13 @@ async function modelFactory () {
|
|
|
130
135
|
} else if (isPlainObject(value)) ({ model, field, query } = value)
|
|
131
136
|
else if (isArray(value)) [model, field, query] = value
|
|
132
137
|
else return
|
|
138
|
+
for (const key in lookupValue) {
|
|
139
|
+
query = query.replaceAll(`{${key}}`, lookupValue[key])
|
|
140
|
+
}
|
|
133
141
|
if (isEmpty(field)) field = 'id'
|
|
134
142
|
const { getModel } = this.app.dobo
|
|
135
143
|
const ref = getModel(model)
|
|
136
|
-
const opts = {
|
|
144
|
+
const opts = { ...options, noCache: true, noMagic: true }
|
|
137
145
|
opts.dataOnly = true
|
|
138
146
|
const rec = await ref.findOneRecord({ query }, opts)
|
|
139
147
|
return get(rec, field, null)
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-22
|
|
4
|
+
|
|
5
|
+
- [2.25.0] Add ```array``` & ```object``` validator handling
|
|
6
|
+
- [2.25.0] Change ```interSite``` definition to ```xSite```
|
|
7
|
+
- [2.25.0] Add ```model.getNonVirtualProperties()```
|
|
8
|
+
- [2.25.0] Bug fix in ```model.loadFixtures()```
|
|
9
|
+
- [2.25.0] Handle ```array``` validation schema
|
|
10
|
+
|
|
11
|
+
## 2026-05-16
|
|
12
|
+
|
|
13
|
+
- [2.24.0] Change ```dobo:immutable``` feature, field no longer hidden
|
|
14
|
+
- [2.24.0] Add ```dobo:[before|after]Driver<Action>``` hook
|
|
15
|
+
- [2.24.0] Add ```dobo.<modelName>:[before|after]Driver<Action>``` hook
|
|
16
|
+
- [2.24.0] Change ```model._simpleLookup()```
|
|
17
|
+
- [2.24.0] Remove ```dobo:[before|after]Build[Query|Search]``` hook
|
|
18
|
+
- [2.24.0] Change ```model.loadFixtures()```
|
|
19
|
+
- [2.24.0] Bugfix in ```model.sanitizeBody()```
|
|
20
|
+
- [2.24.0] Add ```dobo:afterTransaction``` hook
|
|
21
|
+
- [2.24.0] Add ```dobo.<modelName>:afterTransaction``` hook
|
|
22
|
+
|
|
3
23
|
## 2026-05-11
|
|
4
24
|
|
|
5
25
|
- [2.23.0] Add ```beforeBulkCreate``` model hook on ```dobo:unique``` feature
|