dobo 2.20.1 → 2.21.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.
- package/extend/bajo/intl/en-US.json +1 -0
- package/extend/bajo/intl/id.json +1 -0
- package/extend/dobo/feature/dt.js +3 -3
- package/index.js +1 -1
- package/lib/collect-models.js +36 -15
- package/lib/factory/driver.js +29 -20
- package/lib/factory/model/_util.js +33 -13
- package/lib/factory/model/create-record.js +1 -1
- package/lib/factory/model/find-all-record.js +1 -1
- package/lib/factory/model/find-record.js +1 -2
- package/lib/factory/model/get-record.js +1 -1
- package/lib/factory/model/load-fixtures.js +21 -31
- package/lib/factory/model/sanitize-record.js +17 -21
- package/lib/factory/model/update-record.js +1 -1
- package/lib/factory/model/upsert-record.js +1 -1
- package/lib/factory/model.js +14 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +21 -0
|
@@ -150,6 +150,7 @@
|
|
|
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
152
|
"sanitizeBodyError": "Error sanitizing body",
|
|
153
|
+
"virtualFieldIn%s%s%s": "Virtual field '%s' can't be used in '%s' on %s",
|
|
153
154
|
"field": {
|
|
154
155
|
"id": "ID",
|
|
155
156
|
"code": "Kode",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -148,6 +148,7 @@
|
|
|
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
150
|
"sanitizeBodyError": "Kesalahan saat sanitasi body",
|
|
151
|
+
"virtualFieldIn%s%s%s": "Kolom virtual '%s' tidak bisa digunakan di '%s' pada %s",
|
|
151
152
|
"field": {
|
|
152
153
|
"id": "ID",
|
|
153
154
|
"code": "Kode",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
async function dt (opts = {}) {
|
|
2
2
|
opts.field = opts.field ?? 'dt'
|
|
3
|
-
opts.type = opts.type ??'datetime'
|
|
3
|
+
opts.type = opts.type ?? 'datetime'
|
|
4
4
|
opts.formatInt = opts.formatInt ?? false
|
|
5
5
|
opts.formatValueInt = opts.formatValueInt ?? false
|
|
6
6
|
const prop = {
|
|
@@ -12,13 +12,13 @@ async function dt (opts = {}) {
|
|
|
12
12
|
if (opts.type === 'integer') {
|
|
13
13
|
if (opts.formatInt) {
|
|
14
14
|
prop.format = async function (val, data, { req } = {}) {
|
|
15
|
-
const dt = new Date(data
|
|
15
|
+
const dt = new Date(data[opts.field])
|
|
16
16
|
return req ? req.format(dt, 'datetime') : this.app.bajo.format(dt, 'datetime', { lang: req.lang })
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
if (opts.formatValueInt) {
|
|
20
20
|
prop.format = async function (val, data, { req } = {}) {
|
|
21
|
-
return new Date(data
|
|
21
|
+
return new Date(data[opts.field])
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
package/index.js
CHANGED
|
@@ -82,7 +82,7 @@ const propertyType = {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const commonPropertyTypes = ['name', 'type', 'required', 'rules', 'validator', 'ref', 'default', 'values', 'rulesMsg', 'immutable', 'feature', 'format']
|
|
85
|
+
const commonPropertyTypes = ['name', 'type', 'required', 'rules', 'validator', 'ref', 'default', 'values', 'rulesMsg', 'immutable', 'feature', 'format', 'getValue', 'virtual']
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Plugin factory
|
package/lib/collect-models.js
CHANGED
|
@@ -28,20 +28,30 @@ async function sanitizeProp (model, prop, indexes) {
|
|
|
28
28
|
return { value: item, text: item }
|
|
29
29
|
})
|
|
30
30
|
} else if (!isString(prop.values)) delete prop.values
|
|
31
|
-
if (prop.index) {
|
|
32
|
-
if (prop.index === true || prop.index === 'true') prop.index = 'index'
|
|
33
|
-
const [idx, idxName] = prop.index.split(':')
|
|
34
|
-
const index = { name: idxName ?? `${model.collName}_${prop.name}_${idx}`, fields: [prop.name], type: idx }
|
|
35
|
-
indexes.push(index)
|
|
36
|
-
}
|
|
37
31
|
if (prop.hidden) model.hidden.push(prop.name)
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
if (prop.scanable) model.scanables.push(prop.scanable)
|
|
33
|
+
if (prop.virtual) {
|
|
34
|
+
const keys = Object.keys(propType)
|
|
35
|
+
if (!keys.includes(prop.type)) this.fatal('unknownPropType%s%s', `${prop.name}:${prop.type}`, model.name)
|
|
36
|
+
for (const key of ['required', 'rules', 'index', 'validator', 'ref', 'rulesMsg', 'immutable', 'feature']) {
|
|
37
|
+
delete prop[key]
|
|
38
|
+
}
|
|
39
|
+
model.properties.push(prop)
|
|
40
|
+
} else {
|
|
41
|
+
if (prop.index) {
|
|
42
|
+
if (prop.index === true || prop.index === 'true') prop.index = 'index'
|
|
43
|
+
const [idx, idxName] = prop.index.split(':')
|
|
44
|
+
const index = { name: idxName ?? `${model.collName}_${prop.name}_${idx}`, fields: [prop.name], type: idx }
|
|
45
|
+
indexes.push(index)
|
|
46
|
+
}
|
|
47
|
+
if (keys(propType).includes(prop.type)) model.properties.push(pick(prop, allPropKeys))
|
|
48
|
+
else {
|
|
49
|
+
const feature = this.getFeature(prop.type)
|
|
50
|
+
if (!feature) this.fatal('unknownPropType%s%s', prop.type, model.name)
|
|
51
|
+
const opts = omit(prop, ['name', 'type'])
|
|
52
|
+
opts.field = prop.name
|
|
53
|
+
await applyFeature.call(this, model, feature, opts, indexes)
|
|
54
|
+
}
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -151,7 +161,7 @@ export async function sanitizeRef (model, models) {
|
|
|
151
161
|
}
|
|
152
162
|
ref.field = ref.field ?? 'id'
|
|
153
163
|
ref.type = ref.type ?? '1:1'
|
|
154
|
-
ref.searchField = ref.searchField ??
|
|
164
|
+
ref.searchField = ref.searchField ?? ref.field
|
|
155
165
|
ref.labelField = ref.labelField ?? ref.searchField
|
|
156
166
|
const rModel = find(models, { name: ref.model })
|
|
157
167
|
if (!rModel) {
|
|
@@ -309,7 +319,7 @@ async function collectModels () {
|
|
|
309
319
|
|
|
310
320
|
const base = path.basename(file, path.extname(file))
|
|
311
321
|
const defName = pascalCase(`${this.alias} ${base}`)
|
|
312
|
-
const item = await readConfig(file, { ns: this.ns, baseNs: me.ns })
|
|
322
|
+
const item = await readConfig(file, { ns: this.ns, baseNs: me.ns, merge: true })
|
|
313
323
|
if (isEmpty(item)) return undefined
|
|
314
324
|
if (!isPlainObject(item)) me.fatal('invalidModel%s', defName)
|
|
315
325
|
item.name = item.name ?? defName
|
|
@@ -330,8 +340,19 @@ async function collectModels () {
|
|
|
330
340
|
const model = new DoboModel(plugin, schema)
|
|
331
341
|
me.models.push(model)
|
|
332
342
|
}
|
|
343
|
+
// last sanitizing & checking
|
|
333
344
|
for (const model of me.models) {
|
|
334
345
|
await sanitizeRef.call(this, model, me.models)
|
|
346
|
+
for (const item of model.indexes) {
|
|
347
|
+
for (const field of item.fields) {
|
|
348
|
+
const prop = model.properties.find(p => p.name === field)
|
|
349
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s%s', field, 'index', model.name)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const field of model.scanables) {
|
|
353
|
+
const prop = model.properties.find(p => p.name === field)
|
|
354
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s%s', field, 'scanable', model.name)
|
|
355
|
+
}
|
|
335
356
|
}
|
|
336
357
|
this.log.debug('collected%s%d', this.t('model'), this.models.length)
|
|
337
358
|
}
|
package/lib/factory/driver.js
CHANGED
|
@@ -12,7 +12,7 @@ const defIdField = {
|
|
|
12
12
|
|
|
13
13
|
async function driverFactory () {
|
|
14
14
|
const { Tools } = this.app.baseClass
|
|
15
|
-
const { cloneDeep, has, uniq, without, isEmpty } = this.app.lib._
|
|
15
|
+
const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction } = this.app.lib._
|
|
16
16
|
const { isSet } = this.app.lib.aneka
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -167,10 +167,17 @@ async function driverFactory () {
|
|
|
167
167
|
return await this.dropModel(model, options)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
getRealFields (model) {
|
|
171
|
+
return model.getProperties({ noVirtual: true, namesOnly: true })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getVirtualFields (model) {
|
|
175
|
+
return model.getVirtualProperties({ namesOnly: true })
|
|
176
|
+
}
|
|
177
|
+
|
|
170
178
|
async _prepBodyForCreate (model, body = {}, options = {}) {
|
|
171
179
|
const { isSet, generateId } = this.app.lib.aneka
|
|
172
|
-
const {
|
|
173
|
-
for (const prop of model.properties) {
|
|
180
|
+
for (const prop of model.getProperties({ noVirtual: true })) {
|
|
174
181
|
if (!isSet(body[prop.name]) && isSet(prop.default)) {
|
|
175
182
|
if (isFunction(prop.default)) body[prop.name] = await prop.default.call(model)
|
|
176
183
|
else if (typeof prop.default !== 'string') body[prop.name] = prop.default
|
|
@@ -198,6 +205,7 @@ async function driverFactory () {
|
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
}
|
|
208
|
+
return pick(body, this.getRealFields(model))
|
|
201
209
|
}
|
|
202
210
|
|
|
203
211
|
async _prepIdForCreate (model, body = {}, options = {}) {
|
|
@@ -221,19 +229,19 @@ async function driverFactory () {
|
|
|
221
229
|
result.warnings.push(...(options.warnings ?? []))
|
|
222
230
|
}
|
|
223
231
|
|
|
224
|
-
async _createRecord (model,
|
|
232
|
+
async _createRecord (model, input = {}, options = {}) {
|
|
225
233
|
const { isSet } = this.app.lib.aneka
|
|
226
|
-
await this._prepBodyForCreate(model,
|
|
234
|
+
let body = await this._prepBodyForCreate(model, input, options)
|
|
227
235
|
await this._prepIdForCreate(model, body, options)
|
|
228
236
|
if (!options.noUniqueCheck) {
|
|
229
237
|
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
230
238
|
}
|
|
231
239
|
if (!options.noIdCheck && isSet(body.id)) {
|
|
232
|
-
const resp = await this.getRecord(model, body.id, {
|
|
240
|
+
const resp = await this.getRecord(model, body.id, { noMagic: true })
|
|
233
241
|
if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
|
|
234
242
|
}
|
|
235
|
-
|
|
236
|
-
const result = await this.createRecord(model,
|
|
243
|
+
body = this.sanitizeBody(model, body)
|
|
244
|
+
const result = await this.createRecord(model, body, options)
|
|
237
245
|
if (options.noResult) return
|
|
238
246
|
result.data = this.sanitizeRecord(model, result.data)
|
|
239
247
|
this._injectMeta(result, options)
|
|
@@ -245,8 +253,7 @@ async function driverFactory () {
|
|
|
245
253
|
let { chunkSize = this.maxChunkSize } = options
|
|
246
254
|
if (chunkSize > this.maxChunkSize) chunkSize = this.maxChunkSize
|
|
247
255
|
for (const idx in bodies) {
|
|
248
|
-
const body = bodies[idx]
|
|
249
|
-
await this._prepBodyForCreate(model, body, options)
|
|
256
|
+
const body = await this._prepBodyForCreate(model, bodies[idx], options)
|
|
250
257
|
await this._prepIdForCreate(model, body, options)
|
|
251
258
|
bodies[idx] = this.sanitizeBody(model, body)
|
|
252
259
|
}
|
|
@@ -264,18 +271,19 @@ async function driverFactory () {
|
|
|
264
271
|
return result
|
|
265
272
|
}
|
|
266
273
|
|
|
267
|
-
async _updateRecord (model, id,
|
|
274
|
+
async _updateRecord (model, id, input = {}, options = {}) {
|
|
275
|
+
let body = omit(input, this.getVirtualFields(model))
|
|
268
276
|
if (!options.noUniqueCheck) {
|
|
269
277
|
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
270
278
|
}
|
|
271
279
|
if (!options._data) {
|
|
272
|
-
const resp = await this.getRecord(model, id, {
|
|
280
|
+
const resp = await this.getRecord(model, id, { noMagic: true })
|
|
273
281
|
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
274
282
|
options._data = resp.data
|
|
275
283
|
}
|
|
276
|
-
|
|
277
|
-
delete
|
|
278
|
-
const result = await this.updateRecord(model, id,
|
|
284
|
+
body = this.sanitizeBody(model, body, true)
|
|
285
|
+
delete body.id
|
|
286
|
+
const result = await this.updateRecord(model, id, body, options)
|
|
279
287
|
if (options.noResult) return
|
|
280
288
|
result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
281
289
|
result.data = this.sanitizeRecord(model, result.data)
|
|
@@ -283,19 +291,20 @@ async function driverFactory () {
|
|
|
283
291
|
return result
|
|
284
292
|
}
|
|
285
293
|
|
|
286
|
-
async _upsertRecord (model,
|
|
294
|
+
async _upsertRecord (model, input = {}, options = {}) {
|
|
295
|
+
let body = omit(input, this.getVirtualFields(model))
|
|
287
296
|
if (!options.noUniqueCheck) {
|
|
288
297
|
if (!this.uniqueIndexSupport) await this._checkUnique(model, body, options)
|
|
289
298
|
}
|
|
290
299
|
if (isSet(body.id)) {
|
|
291
300
|
if (!options._data) {
|
|
292
|
-
const resp = await this.getRecord(model, body.id, {
|
|
301
|
+
const resp = await this.getRecord(model, body.id, { noMagic: true })
|
|
293
302
|
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
|
|
294
303
|
options._data = resp.data
|
|
295
304
|
}
|
|
296
305
|
}
|
|
297
|
-
|
|
298
|
-
const result = await this.upsertRecord(model,
|
|
306
|
+
body = this.sanitizeBody(model, body)
|
|
307
|
+
const result = await this.upsertRecord(model, body, options)
|
|
299
308
|
if (options.noResult) return
|
|
300
309
|
if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
301
310
|
result.data = this.sanitizeRecord(model, result.data)
|
|
@@ -305,7 +314,7 @@ async function driverFactory () {
|
|
|
305
314
|
|
|
306
315
|
async _removeRecord (model, id, options = {}) {
|
|
307
316
|
if (!options._data) {
|
|
308
|
-
const resp = await this.getRecord(model, id, {
|
|
317
|
+
const resp = await this.getRecord(model, id, { noMagic: true })
|
|
309
318
|
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
310
319
|
options._data = resp.data
|
|
311
320
|
}
|
|
@@ -173,10 +173,10 @@ export async function handleAttachmentUpload (id, trigger, options = {}) {
|
|
|
173
173
|
async function _getRef ({ ref, rModel, prop, key, options, filter } = {}) {
|
|
174
174
|
if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) return
|
|
175
175
|
if (ref.fields.length === 0) return
|
|
176
|
-
const {
|
|
176
|
+
const { fmt } = options
|
|
177
177
|
const fields = [...ref.fields]
|
|
178
178
|
if (!fields.includes(prop.name)) fields.push(prop.name)
|
|
179
|
-
const rOptions = { dataOnly: true, refs: [],
|
|
179
|
+
const rOptions = { dataOnly: true, refs: [], fmt, fields }
|
|
180
180
|
const results = await rModel.findRecord(filter, rOptions)
|
|
181
181
|
return { rOptions, results }
|
|
182
182
|
}
|
|
@@ -232,9 +232,9 @@ export async function getMultiRefs (records = [], options = {}) {
|
|
|
232
232
|
if (!rModel) return
|
|
233
233
|
let matches = []
|
|
234
234
|
for (const r of records) {
|
|
235
|
-
matches.push(rModel.sanitizeId(r[prop.name]))
|
|
235
|
+
matches.push(prop.name === 'id' ? rModel.sanitizeId(r[prop.name]) : r[prop.name])
|
|
236
236
|
}
|
|
237
|
-
matches = uniq(without(matches, undefined, null, NaN))
|
|
237
|
+
matches = uniq(without(matches, undefined, null, NaN)).map(i => i + '')
|
|
238
238
|
let query = {}
|
|
239
239
|
query[ref.field] = { $in: matches }
|
|
240
240
|
if (ref.query) query = { $and: [query, parseQuery(ref.query, rModel)] }
|
|
@@ -290,14 +290,20 @@ function sanitizeQuery (query = {}, parent) {
|
|
|
290
290
|
|
|
291
291
|
keys.forEach(k => {
|
|
292
292
|
const v = obj[k]
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
293
|
+
const props = this.getProperties({ noVirtual: true })
|
|
294
|
+
const fields = props.map(p => p.name)
|
|
295
|
+
const prop = find(props, { name: k })
|
|
296
|
+
if (k[0] !== '$' && !fields.includes(k)) {
|
|
297
|
+
delete keys[k]
|
|
298
|
+
} else {
|
|
299
|
+
if (isPlainObject(v)) obj[k] = sanitizeQuery.call(this, v, k)
|
|
300
|
+
else if (isArray(v)) {
|
|
301
|
+
v.forEach((i, idx) => {
|
|
302
|
+
if (isPlainObject(i)) obj[k][idx] = sanitizeQuery.call(this, i, k)
|
|
303
|
+
else obj[k][idx] = sanitizeField(prop, i)
|
|
304
|
+
})
|
|
305
|
+
} else obj[k] = sanitizeChild(k, v, parent)
|
|
306
|
+
}
|
|
301
307
|
})
|
|
302
308
|
return obj
|
|
303
309
|
}
|
|
@@ -432,10 +438,24 @@ export function preparePagination (filter = {}, options = {}) {
|
|
|
432
438
|
}
|
|
433
439
|
|
|
434
440
|
export async function clearCache (id) {
|
|
435
|
-
const { clear } = this.app.bajoCache
|
|
441
|
+
const { clear } = this.app.bajoCache ?? {}
|
|
436
442
|
if (!clear) return
|
|
437
443
|
await clear({ key: `dobo|${this.name}|getRecord|${id}` })
|
|
438
444
|
await clear({ key: `dobo|${this.name}|findRecord` })
|
|
439
445
|
await clear({ key: `dobo|${this.name}|findAllRecord` })
|
|
440
446
|
await clear({ key: `dobo|${this.name}|findOneRecord` })
|
|
441
447
|
}
|
|
448
|
+
|
|
449
|
+
export async function buildPropValues (prop, opts) {
|
|
450
|
+
const { isString, camelCase } = this.app.lib._
|
|
451
|
+
const { callHandler } = this.app.bajo
|
|
452
|
+
const values = (isString(prop.values) ? await callHandler(prop.values) : [...prop.values]).map(v => {
|
|
453
|
+
if (isString(v)) v = { value: v, text: v }
|
|
454
|
+
if (opts.req) {
|
|
455
|
+
const key = camelCase(`${prop.name} ${v.text}`)
|
|
456
|
+
if (opts.req.te(key)) v.text = opts.req.t(key)
|
|
457
|
+
}
|
|
458
|
+
return v
|
|
459
|
+
})
|
|
460
|
+
return values
|
|
461
|
+
}
|
|
@@ -24,8 +24,8 @@ async function createRecord (...args) {
|
|
|
24
24
|
result = result ?? {}
|
|
25
25
|
const { warnings } = getDefaultValues(options)
|
|
26
26
|
if (!warnings) delete result.warnings
|
|
27
|
-
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
28
27
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
28
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
29
29
|
await handleReq.call(this, result.data.id, 'created', options)
|
|
30
30
|
await execDynHook.call(this, 'afterCreateRecord', input, result, options)
|
|
31
31
|
await execModelHook.call(this, 'afterCreateRecord', input, result, options)
|
|
@@ -37,12 +37,12 @@ async function native (...args) {
|
|
|
37
37
|
result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
|
|
38
38
|
if (!warnings) delete result.warnings
|
|
39
39
|
|
|
40
|
+
if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
|
|
40
41
|
if (!noResultSanitizer) {
|
|
41
42
|
for (const idx in result.data) {
|
|
42
43
|
result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
|
|
46
46
|
await execDynHook.call(this, 'afterFindRecord', filter, result, options)
|
|
47
47
|
await execModelHook.call(this, 'afterFindRecord', filter, result, options)
|
|
48
48
|
await execHook.call(this, 'afterFindRecord', filter, result, options)
|
|
@@ -101,13 +101,12 @@ async function findRecord (...args) {
|
|
|
101
101
|
}
|
|
102
102
|
result.pages = options.count ? Math.ceil(result.count / filter.limit) : undefined
|
|
103
103
|
if (!warnings) delete result.warnings
|
|
104
|
-
|
|
104
|
+
if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
|
|
105
105
|
if (!noResultSanitizer) {
|
|
106
106
|
for (const idx in result.data) {
|
|
107
107
|
result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
|
|
111
110
|
await execDynHook.call(this, 'afterFindRecord', filter, result, options)
|
|
112
111
|
await execModelHook.call(this, 'afterFindRecord', filter, result, options)
|
|
113
112
|
await execHook.call(this, 'afterFindRecord', filter, result, options)
|
|
@@ -68,8 +68,8 @@ async function getRecord (...args) {
|
|
|
68
68
|
const { warnings } = getDefaultValues(options)
|
|
69
69
|
if (!warnings) delete result.warnings
|
|
70
70
|
if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
|
|
71
|
-
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
72
71
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
72
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
73
73
|
await execDynHook.call(this, 'afterGetRecord', id, result, options)
|
|
74
74
|
await execModelHook.call(this, 'afterGetRecord', id, result, options)
|
|
75
75
|
await execHook.call(this, 'afterGetRecord', id, result, options)
|
|
@@ -4,49 +4,39 @@ async function exec ({ item, spinner, options, result, items } = {}) {
|
|
|
4
4
|
const { isArray, isString } = this.app.lib._
|
|
5
5
|
const { getPluginDataDir } = this.app.bajo
|
|
6
6
|
const { fs } = this.app.lib
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
|
|
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) {
|
|
12
|
+
if (isString(att)) att = { field: 'file', file: att }
|
|
13
|
+
const fname = path.basename(att.file)
|
|
14
|
+
if (fs.existsSync(att.file)) {
|
|
15
|
+
const dest = `${getPluginDataDir(this.plugin.ns)}/${resp.id}/${att.field}/${fname}`
|
|
16
|
+
try {
|
|
17
|
+
fs.copySync(att.file, dest)
|
|
18
|
+
} catch (err) {}
|
|
19
|
+
}
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
|
-
}
|
|
22
|
+
})
|
|
20
23
|
result.success++
|
|
21
24
|
if (spinner) spinner.setText('recordsAdded%s%d%d', this.name, result.success, items.length)
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
async function loadFixtures ({ spinner, ignoreError = true, collectItems = false, noLookup = false } = {}, options = {}) {
|
|
25
|
-
const { readConfig
|
|
28
|
+
const { readConfig } = this.app.bajo
|
|
26
29
|
const { resolvePath } = this.app.lib.aneka
|
|
27
|
-
const { isEmpty
|
|
30
|
+
const { isEmpty } = this.app.lib._
|
|
28
31
|
if (this.connection.proxy) {
|
|
29
32
|
this.log.warn('proxiedConnBound%s', this.name)
|
|
30
33
|
return
|
|
31
34
|
}
|
|
32
35
|
const result = { success: 0, failed: 0 }
|
|
33
36
|
const base = path.basename(this.file, path.extname(this.file))
|
|
34
|
-
// original
|
|
35
37
|
const pattern = resolvePath(`${path.dirname(this.file)}/../fixture/${base}.*`)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// override
|
|
39
|
-
const overrides = await readConfig(`${this.app.main.dir.pkg}/extend/dobo/override/${this.plugin.ns}/fixture/${base}.*`, { ns: this.plugin.ns, ignoreError: true })
|
|
40
|
-
|
|
41
|
-
if (isArray(overrides) && !isEmpty(overrides)) items = overrides
|
|
42
|
-
// extend
|
|
43
|
-
const me = this
|
|
44
|
-
await eachPlugins(async function ({ dir }) {
|
|
45
|
-
const { ns } = this
|
|
46
|
-
const extend = await readConfig(`${dir}/extend/dobo/extend/${me.plugin.ns}/fixture/${base}.*`, { ns, ignoreError: true })
|
|
47
|
-
if (isArray(extend) && !isEmpty(extend)) items.push(...extend)
|
|
48
|
-
})
|
|
49
|
-
const opts = { noHook: true, noCache: true, ...(omit(options, ['noHook', 'noCache'])) }
|
|
38
|
+
const items = await readConfig(pattern, { ns: this.plugin.ns, baseNs: 'dobo', checkOverride: true, defValue: [] })
|
|
39
|
+
const opts = { ...options, noMagic: true }
|
|
50
40
|
for (const item of items) {
|
|
51
41
|
for (const k in item) {
|
|
52
42
|
const v = item[k]
|
|
@@ -63,14 +53,14 @@ async function loadFixtures ({ spinner, ignoreError = true, collectItems = false
|
|
|
63
53
|
for (const item of items) {
|
|
64
54
|
if (ignoreError) {
|
|
65
55
|
try {
|
|
66
|
-
await exec.call(this, { item, spinner,
|
|
56
|
+
await exec.call(this, { item, spinner, options, result, items })
|
|
67
57
|
} catch (err) {
|
|
68
|
-
|
|
58
|
+
if (this.app.bajo.config.log.applet) console.error(err)
|
|
69
59
|
err.model = this.name
|
|
70
60
|
if (this.app.applet) this.plugin.print.fail(this.app.dobo.validationErrorMessage(err))
|
|
71
61
|
result.failed++
|
|
72
62
|
}
|
|
73
|
-
} else await exec.call(this, { item, spinner,
|
|
63
|
+
} else await exec.call(this, { item, spinner, options, result, items })
|
|
74
64
|
}
|
|
75
65
|
return result
|
|
76
66
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildPropValues } from './_util.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Sanitize record to conform with the model's definition
|
|
3
5
|
*
|
|
@@ -23,43 +25,37 @@ async function sanitizeRecord (record = {}, opts = {}) {
|
|
|
23
25
|
newFields = without(newFields, ...allHidden)
|
|
24
26
|
const body = fillObject(record, newFields, null)
|
|
25
27
|
const newRecord = await this.sanitizeBody({ body, noDefault: true })
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
+
if (record._ref) newRecord._ref = cloneDeep(record._ref)
|
|
29
|
+
for (const key in newRecord) {
|
|
30
|
+
const prop = this.getProperty(key)
|
|
31
|
+
if (!prop) continue
|
|
32
|
+
const val = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
|
|
33
|
+
if (isFunction(prop.getValue)) newRecord[key] = await prop.getValue.call(this, val, newRecord, opts)
|
|
34
|
+
else if (isString(prop.getValue)) newRecord[key] = await callHandler(this.plugin, this, val, newRecord, opts)
|
|
35
|
+
}
|
|
36
|
+
if (opts.fmt) {
|
|
37
|
+
newRecord._fmt = cloneDeep(newRecord)
|
|
28
38
|
for (const key in newRecord) {
|
|
29
39
|
const prop = this.getProperty(key)
|
|
30
40
|
if (!prop) continue
|
|
31
|
-
if (prop.formatValue && opts.retainOriginalValue) {
|
|
32
|
-
const val = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord._orig[key]) : newRecord._orig[key]
|
|
33
|
-
if (isFunction(prop.formatValue)) newRecord._orig[key] = await prop.formatValue.call(this, val, newRecord._orig, opts)
|
|
34
|
-
else if (isString(prop.formatValue)) newRecord._orig[key] = await callHandler(this.plugin, this, val, newRecord._orig, opts)
|
|
35
|
-
}
|
|
36
41
|
let value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
|
|
37
42
|
if (prop.values) {
|
|
38
|
-
const values =
|
|
39
|
-
if (isString(v)) v = { value: v, text: v }
|
|
40
|
-
if (opts.req) {
|
|
41
|
-
const { camelCase } = this.app.lib._
|
|
42
|
-
const key = camelCase(`${prop.name} ${v.text}`)
|
|
43
|
-
if (opts.req.te(key)) v.text = opts.req.t(key)
|
|
44
|
-
}
|
|
45
|
-
return v
|
|
46
|
-
})
|
|
43
|
+
const values = await buildPropValues.call(this, prop, opts)
|
|
47
44
|
value = (values.find(v => v.value === value) ?? {}).text ?? value
|
|
48
45
|
}
|
|
49
|
-
if (prop.format === false) newRecord[key] = value + ''
|
|
50
|
-
else if (isFunction(prop.format)) newRecord[key] = await prop.format.call(this, value, newRecord, opts)
|
|
51
|
-
else if (isString(prop.format)) newRecord[key] = await callHandler(this.plugin, this, value, newRecord, opts)
|
|
46
|
+
if (prop.format === false) newRecord._fmt[key] = value + ''
|
|
47
|
+
else if (isFunction(prop.format)) newRecord._fmt[key] = await prop.format.call(this, value, newRecord, opts)
|
|
48
|
+
else if (isString(prop.format)) newRecord._fmt[key] = await callHandler(this.plugin, this, value, newRecord, opts)
|
|
52
49
|
else {
|
|
53
50
|
const options = {
|
|
54
51
|
lang: get(opts, 'req.lang'),
|
|
55
52
|
latitude: ['lat', 'latitude'].includes(key),
|
|
56
53
|
longitude: ['lon', 'lng', 'longitude'].includes(key)
|
|
57
54
|
}
|
|
58
|
-
newRecord[key] = format(value, prop.type, options)
|
|
55
|
+
newRecord._fmt[key] = format(value, prop.type, options)
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
|
-
if (record._ref) newRecord._ref = record._ref
|
|
63
59
|
return newRecord
|
|
64
60
|
}
|
|
65
61
|
|
|
@@ -71,11 +71,11 @@ async function updateRecord (...args) {
|
|
|
71
71
|
if (noResult) return
|
|
72
72
|
const { warnings } = getDefaultValues(options)
|
|
73
73
|
if (!warnings) delete result.warnings
|
|
74
|
+
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
74
75
|
if (!noResultSanitizer) {
|
|
75
76
|
result.data = await this.sanitizeRecord(result.data, options)
|
|
76
77
|
result.oldData = await this.sanitizeRecord(result.oldData, options)
|
|
77
78
|
}
|
|
78
|
-
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
79
79
|
await handleReq.call(this, result.data.id, 'updated', options)
|
|
80
80
|
await execDynHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
81
81
|
await execModelHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
@@ -20,8 +20,8 @@ async function native (body = {}, opts = {}) {
|
|
|
20
20
|
if (noResult) return
|
|
21
21
|
const { warnings } = getDefaultValues(options)
|
|
22
22
|
if (!warnings) delete result.warnings
|
|
23
|
-
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
24
23
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
24
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
25
25
|
await handleReq.call(this, result.data.id, 'upserted', options)
|
|
26
26
|
await execDynHook.call(this, 'afterUpsertRecord', input, result, options)
|
|
27
27
|
await execModelHook.call(this, 'afterUpsertRecord', input, result, options)
|
package/lib/factory/model.js
CHANGED
|
@@ -91,6 +91,20 @@ async function modelFactory () {
|
|
|
91
91
|
return this.properties.find(prop => prop.name === name)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
getProperties = ({ noVirtual, namesOnly } = {}) => {
|
|
95
|
+
const items = noVirtual ? this.properties.filter(prop => !prop.virtual) : this.properties
|
|
96
|
+
return namesOnly ? items.map(item => item.name) : items
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getVirtualProperties = ({ namesOnly }) => {
|
|
100
|
+
const items = this.properties.filter(prop => prop.virtual)
|
|
101
|
+
return namesOnly ? items.map(item => item.name) : items
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getIndexes = () => {
|
|
105
|
+
return this.indexes
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
hasProperty = (name) => {
|
|
95
109
|
return !!this.getProperty(name)
|
|
96
110
|
}
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-04-28
|
|
4
|
+
|
|
5
|
+
- [2.21.1] Bug fix in ```collect-models.js```
|
|
6
|
+
- [2.21.1] Bug fix in ```driver._updateRecord()```
|
|
7
|
+
- [2.21.1] Bug fix in ```util.getMultiRef()```
|
|
8
|
+
- [2.21.1] Bug fix in setting references
|
|
9
|
+
- [2.21.1] Bug fix in ```model.sanitizeRecord()```
|
|
10
|
+
|
|
11
|
+
## 2026-04-25
|
|
12
|
+
|
|
13
|
+
- [2.21.0] Change ```options.formatValue``` to ```options.fmt```
|
|
14
|
+
- [2.21.0] Remove ```options.retainOriginalValue``` since it is not needed anymore
|
|
15
|
+
- [2.21.0] Remove ```formatValue``` altogether
|
|
16
|
+
- [2.21.0] Remove ```record._orig```, in exchange switch the formatted value to ```record._fmt```
|
|
17
|
+
- [2.21.0] Add ```property.virtual``` to set property is a virtual/calculated property
|
|
18
|
+
- [2.21.0] Add necessary safe guards for virtual property
|
|
19
|
+
- [2.21.0] Add ```model.getProperties()```
|
|
20
|
+
- [2.21.0] Add ```model.getIndexes()```
|
|
21
|
+
- [2.21.0] Add ```model.getVirtualPropertties()```
|
|
22
|
+
- [2.21.0] Activate transaction on ```loadFixtures()```
|
|
23
|
+
|
|
3
24
|
## 2026-04-23
|
|
4
25
|
|
|
5
26
|
- [2.20.1] Bug fix in ```collect-models.js```, now with deep merge in advance
|