dobo 2.3.0 → 2.4.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/index.js +9 -0
- package/lib/collect-models.js +9 -5
- package/lib/factory/driver.js +41 -5
- package/lib/factory/model/_util.js +9 -16
- package/lib/factory/model/bulk-create-records.js +36 -0
- package/lib/factory/model/find-all-record.js +2 -1
- package/lib/factory/model.js +3 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +13 -0
package/index.js
CHANGED
|
@@ -480,6 +480,15 @@ async function factory (pkgName) {
|
|
|
480
480
|
if (isEmpty(type)) throw this.error('histogramTypeMissing')
|
|
481
481
|
if (!this.constructor.histogramTypes.includes(type)) throw this.error('unsupportedHistogramType%s', type)
|
|
482
482
|
}
|
|
483
|
+
|
|
484
|
+
execModelHook = async (model, hookName, ...args) => {
|
|
485
|
+
const { orderBy } = this.app.lib._
|
|
486
|
+
const hooks = orderBy(model.hooks.filter(hook => hook.name === hookName), ['level'])
|
|
487
|
+
for (const hook of hooks) {
|
|
488
|
+
if (hook.noWait) hook.handler.call(model, ...args)
|
|
489
|
+
else await hook.handler.call(model, ...args)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
483
492
|
}
|
|
484
493
|
|
|
485
494
|
return Dobo
|
package/lib/collect-models.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import modelFactory from './factory/model.js'
|
|
3
|
+
import actionFactory from './factory/action.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Sanitize one single property of a model
|
|
@@ -126,7 +127,7 @@ async function findAllIndexes (model, inputs = []) {
|
|
|
126
127
|
* @param {Object} model - Model
|
|
127
128
|
* @param {Array} [models] - All model match agaist. Defaults to ```dobo.models```
|
|
128
129
|
*/
|
|
129
|
-
export async function sanitizeRef (model, models
|
|
130
|
+
export async function sanitizeRef (model, models) {
|
|
130
131
|
const { find, isString, pullAt } = this.app.lib._
|
|
131
132
|
if (!models) models = this.models
|
|
132
133
|
for (const prop of model.properties) {
|
|
@@ -139,13 +140,15 @@ export async function sanitizeRef (model, models, fatal) {
|
|
|
139
140
|
ref.type = ref.type ?? '1:1'
|
|
140
141
|
const rModel = find(models, { name: ref.model })
|
|
141
142
|
if (!rModel) {
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
ignored.push(key)
|
|
144
|
+
this.log.warn('notFound%s%s', this.t('model'), ref.model)
|
|
145
|
+
continue
|
|
144
146
|
}
|
|
145
147
|
const rProp = find(rModel.properties, { name: ref.propName })
|
|
146
148
|
if (!rProp) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
ignored.push(key)
|
|
150
|
+
this.log.warn('notFound%s%s', this.t('property'), `${ref.propName}@${ref.model}`)
|
|
151
|
+
continue
|
|
149
152
|
}
|
|
150
153
|
ref.fields = ref.fields ?? '*'
|
|
151
154
|
if (['*', 'all'].includes(ref.fields)) ref.fields = rModel.properties.map(p => p.name)
|
|
@@ -283,6 +286,7 @@ async function createSchema (item) {
|
|
|
283
286
|
async function collectModels () {
|
|
284
287
|
const { eachPlugins } = this.app.bajo
|
|
285
288
|
const { orderBy, has } = this.app.lib._
|
|
289
|
+
await actionFactory.call(this)
|
|
286
290
|
const DoboModel = await modelFactory.call(this)
|
|
287
291
|
|
|
288
292
|
this.log.trace('collecting%s', this.t('model'))
|
package/lib/factory/driver.js
CHANGED
|
@@ -37,6 +37,7 @@ async function driverFactory () {
|
|
|
37
37
|
uniqueIndex: false,
|
|
38
38
|
nullableField: true
|
|
39
39
|
}
|
|
40
|
+
this.maxChunkSize = 500
|
|
40
41
|
this.memory = false
|
|
41
42
|
this.options = options
|
|
42
43
|
}
|
|
@@ -154,7 +155,7 @@ async function driverFactory () {
|
|
|
154
155
|
return await this.dropModel(model, options)
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
async
|
|
158
|
+
async _prepBodyForCreate (model, body = {}, options = {}) {
|
|
158
159
|
const { isSet, generateId } = this.app.lib.aneka
|
|
159
160
|
const { isFunction } = this.app.lib._
|
|
160
161
|
for (const prop of model.properties) {
|
|
@@ -185,6 +186,11 @@ async function driverFactory () {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async _prepIdForCreate (model, body = {}, options = {}) {
|
|
192
|
+
const { isSet, generateId } = this.app.lib.aneka
|
|
193
|
+
const { isFunction } = this.app.lib._
|
|
188
194
|
const prop = model.properties.find(p => p.name === 'id')
|
|
189
195
|
if (!isSet(body.id) && prop.type === 'string') {
|
|
190
196
|
if (this.idGenerator) {
|
|
@@ -196,17 +202,42 @@ async function driverFactory () {
|
|
|
196
202
|
if (!body.id) body.id = ulid()
|
|
197
203
|
body.id = body.id.slice(0, prop.maxLength)
|
|
198
204
|
}
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async _createRecord (model, body = {}, options = {}) {
|
|
208
|
+
const { isSet } = this.app.lib.aneka
|
|
209
|
+
await this._prepBodyForCreate(model, body, options)
|
|
210
|
+
await this._prepIdForCreate(model, body, options)
|
|
211
|
+
if (!options.noUniqueCheck) {
|
|
212
|
+
if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
|
|
213
|
+
}
|
|
214
|
+
if (!options.noIdCheck && isSet(body.id)) {
|
|
201
215
|
const resp = await this.getRecord(model, body.id, { noHook: true })
|
|
202
216
|
if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
|
|
203
217
|
}
|
|
204
|
-
const
|
|
218
|
+
const input = this.sanitizeBody(model, body)
|
|
219
|
+
const result = await this.createRecord(model, input, options)
|
|
205
220
|
if (options.noResult) return
|
|
206
221
|
result.data = this.sanitizeRecord(model, result.data)
|
|
207
222
|
return result
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
async _bulkCreateRecords (model, bodies = [], options = {}) {
|
|
226
|
+
const { chunk } = this.app.lib._
|
|
227
|
+
let { chunkSize = this.maxChunkSize } = options
|
|
228
|
+
if (chunkSize > this.maxChunkSize) chunkSize = this.maxChunkSize
|
|
229
|
+
for (const idx in bodies) {
|
|
230
|
+
const body = bodies[idx]
|
|
231
|
+
await this._prepBodyForCreate(model, body, options)
|
|
232
|
+
await this._prepIdForCreate(model, body, options)
|
|
233
|
+
bodies[idx] = this.sanitizeBody(model, body)
|
|
234
|
+
}
|
|
235
|
+
const items = chunk(bodies, chunkSize)
|
|
236
|
+
for (const item of items) {
|
|
237
|
+
await this.bulkCreateRecords(model, item, options)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
210
241
|
async _getRecord (model, id, options = {}) {
|
|
211
242
|
const result = await this.getRecord(model, id, options)
|
|
212
243
|
if (isEmpty(result.data) && options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
|
|
@@ -235,7 +266,8 @@ async function driverFactory () {
|
|
|
235
266
|
if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
|
|
236
267
|
options._data = resp.data
|
|
237
268
|
}
|
|
238
|
-
const
|
|
269
|
+
const input = this.sanitizeBody(model, body)
|
|
270
|
+
const result = await this.upsertRecord(model, input, options)
|
|
239
271
|
if (options.noResult) return
|
|
240
272
|
if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
|
|
241
273
|
result.data = this.sanitizeRecord(model, result.data)
|
|
@@ -359,6 +391,10 @@ async function driverFactory () {
|
|
|
359
391
|
throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'findRecord')
|
|
360
392
|
}
|
|
361
393
|
|
|
394
|
+
async bulkCreateRecords (model, bodies = [], options = {}) {
|
|
395
|
+
throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'bulkCreateRecords')
|
|
396
|
+
}
|
|
397
|
+
|
|
362
398
|
async countRecord (model, filter = {}, options = {}) {
|
|
363
399
|
throw this.plugin.error('notSupported%s%s', this.app.t('method'), 'countRecord')
|
|
364
400
|
}
|
|
@@ -13,17 +13,10 @@ export async function execHook (name, ...args) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export async function execModelHook (name, ...args) {
|
|
16
|
-
const { last
|
|
16
|
+
const { last } = this.app.lib._
|
|
17
|
+
const { execModelHook } = this.app.dobo
|
|
17
18
|
const { noModelHook } = last(args)
|
|
18
|
-
|
|
19
|
-
if (!noModelHook) {
|
|
20
|
-
const hooks = orderBy(this.hooks.filter(hook => hook.name === name), ['level'])
|
|
21
|
-
for (const hook of hooks) {
|
|
22
|
-
if (hook.noWait) hook.handler.call(this, ...args)
|
|
23
|
-
else await hook.handler.call(this, ...args)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return results
|
|
19
|
+
if (!noModelHook) await execModelHook(this, name, ...args)
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export async function execDynHook (name, ...args) {
|
|
@@ -189,7 +182,7 @@ export async function getSingleRef (record = {}, options = {}) {
|
|
|
189
182
|
|
|
190
183
|
export async function getMultiRefs (records = [], options = {}) {
|
|
191
184
|
const { isSet } = this.app.lib.aneka
|
|
192
|
-
const { uniq,
|
|
185
|
+
const { uniq, without, get } = this.app.lib._
|
|
193
186
|
const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
|
|
194
187
|
options.refs = options.refs ?? []
|
|
195
188
|
if (props.length > 0) {
|
|
@@ -201,11 +194,11 @@ export async function getMultiRefs (records = [], options = {}) {
|
|
|
201
194
|
if (ref.type !== '1:1') continue
|
|
202
195
|
if (get(records, `0._ref.${key}`)) continue
|
|
203
196
|
const rModel = this.app.dobo.getModel(ref.model)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
let matches = []
|
|
198
|
+
for (const r of records) {
|
|
199
|
+
matches.push(rModel.sanitizeId(r[prop.name]))
|
|
200
|
+
}
|
|
201
|
+
matches = uniq(without(matches, undefined, null, NaN))
|
|
209
202
|
const query = {}
|
|
210
203
|
query[ref.propName] = { $in: matches }
|
|
211
204
|
const rFilter = { query, limit: matches.length }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook } from './_util.js'
|
|
2
|
+
|
|
3
|
+
export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
|
|
4
|
+
const action = 'bulkCreateRecords'
|
|
5
|
+
|
|
6
|
+
async function bulkCreateRecords (...args) {
|
|
7
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
8
|
+
const [bodies = [], opts = {}] = args
|
|
9
|
+
const { cloneDeep, get } = this.app.lib._
|
|
10
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
11
|
+
const { truncateString, noBodySanitizer, noValidation } = options
|
|
12
|
+
const extFields = get(options, 'validation.extFields', [])
|
|
13
|
+
|
|
14
|
+
const inputs = cloneDeep(bodies)
|
|
15
|
+
if (!noBodySanitizer) {
|
|
16
|
+
for (const idx in inputs) {
|
|
17
|
+
inputs[idx] = await this.sanitizeBody({ body: inputs[idx], extFields, strict: true, truncateString, onlyTypes })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await execHook.call(this, 'beforeBulkCreateRecords', inputs, options)
|
|
21
|
+
await execModelHook.call(this, 'beforeBulkCreateRecords', inputs, options)
|
|
22
|
+
await execDynHook.call(this, 'beforeBulkCreateRecords', inputs, options)
|
|
23
|
+
if (!noValidation) {
|
|
24
|
+
for (const input of inputs) {
|
|
25
|
+
await execValidation.call(this, input, options)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// TODO: bulk don't return anything currently, it should return at least a stat
|
|
29
|
+
await this.driver._bulkCreateRecords(this, inputs, options)
|
|
30
|
+
await execDynHook.call(this, 'afterBulkCreateRecords', inputs, options)
|
|
31
|
+
await execModelHook.call(this, 'afterBulkCreateRecords', inputs, options)
|
|
32
|
+
await execHook.call(this, 'afterBulkCreateRecords', inputs, options)
|
|
33
|
+
return []
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default bulkCreateRecords
|
|
@@ -60,7 +60,8 @@ async function loop (params, opts, dataOnly) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
async function findAllRecord (...args) {
|
|
63
|
-
|
|
63
|
+
// can't use action here, because people tends to use is without arguments
|
|
64
|
+
// if (args.length === 0) return this.action(action, ...args)
|
|
64
65
|
const [params = {}, opts = {}] = args
|
|
65
66
|
const { dataOnly = true } = opts
|
|
66
67
|
if (this.driver.findAllRecord) {
|
package/lib/factory/model.js
CHANGED
|
@@ -23,6 +23,7 @@ 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
25
|
import upsertRecord from './model/upsert-record.js'
|
|
26
|
+
import bulkCreateRecords from './model/bulk-create-records.js'
|
|
26
27
|
import listAttachment from './model/list-attachment.js'
|
|
27
28
|
import validate from './model/validate.js'
|
|
28
29
|
|
|
@@ -108,6 +109,8 @@ async function modelFactory () {
|
|
|
108
109
|
findOneRecord = findOneRecord
|
|
109
110
|
findAllRecord = findAllRecord
|
|
110
111
|
|
|
112
|
+
bulkCreateRecords = bulkCreateRecords
|
|
113
|
+
|
|
111
114
|
createAggregate = createAggregate
|
|
112
115
|
createHistogram = createHistogram
|
|
113
116
|
countRecord = countRecord
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-01-29
|
|
4
|
+
|
|
5
|
+
- [2.4.0] Add ```bulkCreateRecords()``` on model & driver
|
|
6
|
+
- [2.4.0] Add ```execModelHook()```
|
|
7
|
+
- [2.4.0] Bug fix on models collection
|
|
8
|
+
- [2.4.0] Add ```DoboAction``` to the ```app.baseClass```
|
|
9
|
+
- [2.4.0] ```findAllRecord()``` can't be called as action
|
|
10
|
+
|
|
11
|
+
## 2026-01-26
|
|
12
|
+
|
|
13
|
+
- [2.3.1] Bug fix if reference model isn't loaded only yield warning
|
|
14
|
+
- [2.3.1] Bug fix on fetching multi references
|
|
15
|
+
|
|
3
16
|
## 2026-01-24
|
|
4
17
|
|
|
5
18
|
- [2.3.0] Add ```dynHooks``` to model's options
|