dobo 2.20.1 → 2.21.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 +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 +33 -13
- package/lib/factory/driver.js +29 -20
- package/lib/factory/model/_util.js +31 -11
- package/lib/factory/model/load-fixtures.js +21 -31
- package/lib/factory/model/sanitize-record.js +16 -20
- package/lib/factory/model.js +14 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +13 -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": "Virtual field 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": "Kolom virtual 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,29 @@ 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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
if (prop.virtual) {
|
|
33
|
+
const keys = Object.keys(propType)
|
|
34
|
+
if (!keys.includes(prop.type)) this.fatal('unknownPropType%s%s', `${prop.name}:${prop.type}`, model.name)
|
|
35
|
+
for (const key of ['required', 'rules', 'index', 'validator', 'ref', 'rulesMsg', 'immutable', 'feature']) {
|
|
36
|
+
delete prop[key]
|
|
37
|
+
}
|
|
38
|
+
model.properties.push(prop)
|
|
39
|
+
} else {
|
|
40
|
+
if (prop.index) {
|
|
41
|
+
if (prop.index === true || prop.index === 'true') prop.index = 'index'
|
|
42
|
+
const [idx, idxName] = prop.index.split(':')
|
|
43
|
+
const index = { name: idxName ?? `${model.collName}_${prop.name}_${idx}`, fields: [prop.name], type: idx }
|
|
44
|
+
indexes.push(index)
|
|
45
|
+
}
|
|
46
|
+
if (keys(propType).includes(prop.type)) model.properties.push(pick(prop, allPropKeys))
|
|
47
|
+
else {
|
|
48
|
+
const feature = this.getFeature(prop.type)
|
|
49
|
+
if (!feature) this.fatal('unknownPropType%s%s', prop.type, model.name)
|
|
50
|
+
const opts = omit(prop, ['name', 'type'])
|
|
51
|
+
opts.field = prop.name
|
|
52
|
+
await applyFeature.call(this, model, feature, opts, indexes)
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
|
|
@@ -330,8 +339,19 @@ async function collectModels () {
|
|
|
330
339
|
const model = new DoboModel(plugin, schema)
|
|
331
340
|
me.models.push(model)
|
|
332
341
|
}
|
|
342
|
+
// last sanitizing & checking
|
|
333
343
|
for (const model of me.models) {
|
|
334
344
|
await sanitizeRef.call(this, model, me.models)
|
|
345
|
+
for (const item of model.indexes) {
|
|
346
|
+
for (const field of item.fields) {
|
|
347
|
+
const prop = model.properties.find(p => p.name === field)
|
|
348
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s', 'index', model.name)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const field of model.scanable) {
|
|
352
|
+
const prop = model.properties.find(p => p.name === field)
|
|
353
|
+
if (!prop || (prop && prop.virtual)) throw this.error('virtualFieldIn%s%s', 'scanable', model.name)
|
|
354
|
+
}
|
|
335
355
|
}
|
|
336
356
|
this.log.debug('collected%s%d', this.t('model'), this.models.length)
|
|
337
357
|
}
|
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())
|
|
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())
|
|
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
|
}
|
|
@@ -234,7 +234,7 @@ export async function getMultiRefs (records = [], options = {}) {
|
|
|
234
234
|
for (const r of records) {
|
|
235
235
|
matches.push(rModel.sanitizeId(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
|
}
|
|
@@ -439,3 +445,17 @@ export async function clearCache (id) {
|
|
|
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
|
+
}
|
|
@@ -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,39 +25,33 @@ 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
|
-
|
|
27
|
-
|
|
28
|
+
for (const key in newRecord) {
|
|
29
|
+
const prop = this.getProperty(key)
|
|
30
|
+
const val = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
|
|
31
|
+
if (isFunction(prop.getValue)) newRecord[key] = await prop.getValue.call(this, val, newRecord, opts)
|
|
32
|
+
else if (isString(prop.getValue)) newRecord[key] = await callHandler(this.plugin, this, val, newRecord, opts)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (opts.fmt) {
|
|
36
|
+
newRecord._fmt = cloneDeep(newRecord)
|
|
28
37
|
for (const key in newRecord) {
|
|
29
38
|
const prop = this.getProperty(key)
|
|
30
39
|
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
40
|
let value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
|
|
37
41
|
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
|
-
})
|
|
42
|
+
const values = await buildPropValues.call(this, prop, opts)
|
|
47
43
|
value = (values.find(v => v.value === value) ?? {}).text ?? value
|
|
48
44
|
}
|
|
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)
|
|
45
|
+
if (prop.format === false) newRecord._fmt[key] = value + ''
|
|
46
|
+
else if (isFunction(prop.format)) newRecord._fmt[key] = await prop.format.call(this, value, newRecord, opts)
|
|
47
|
+
else if (isString(prop.format)) newRecord._fmt[key] = await callHandler(this.plugin, this, value, newRecord, opts)
|
|
52
48
|
else {
|
|
53
49
|
const options = {
|
|
54
50
|
lang: get(opts, 'req.lang'),
|
|
55
51
|
latitude: ['lat', 'latitude'].includes(key),
|
|
56
52
|
longitude: ['lon', 'lng', 'longitude'].includes(key)
|
|
57
53
|
}
|
|
58
|
-
newRecord[key] = format(value, prop.type, options)
|
|
54
|
+
newRecord._fmt[key] = format(value, prop.type, options)
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
}
|
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,18 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-04-25
|
|
4
|
+
|
|
5
|
+
- [2.21.0] Change ```options.formatValue``` to ```options.fmt```
|
|
6
|
+
- [2.21.0] Remove ```options.retainOriginalValue``` since it is not needed anymore
|
|
7
|
+
- [2.21.0] Remove ```formatValue``` altogether
|
|
8
|
+
- [2.21.0] Remove ```record._orig```, in exchange switch the formatted value to ```record._fmt```
|
|
9
|
+
- [2.21.0] Add ```property.virtual``` to set property is a virtual/calculated property
|
|
10
|
+
- [2.21.0] Add necessary safe guards for virtual property
|
|
11
|
+
- [2.21.0] Add ```model.getProperties()```
|
|
12
|
+
- [2.21.0] Add ```model.getIndexes()```
|
|
13
|
+
- [2.21.0] Add ```model.getVirtualPropertties()```
|
|
14
|
+
- [2.21.0] Activate transaction on ```loadFixtures()```
|
|
15
|
+
|
|
3
16
|
## 2026-04-23
|
|
4
17
|
|
|
5
18
|
- [2.20.1] Bug fix in ```collect-models.js```, now with deep merge in advance
|