dobo 2.5.1 → 2.6.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 +1 -1
- package/lib/collect-models.js +3 -1
- package/lib/factory/driver.js +1 -0
- package/lib/factory/model/_util.js +59 -46
- package/package.json +1 -1
- package/wiki/CHANGES.md +6 -0
package/index.js
CHANGED
|
@@ -481,7 +481,7 @@ async function factory (pkgName) {
|
|
|
481
481
|
if (!this.constructor.histogramTypes.includes(type)) throw this.error('unsupportedHistogramType%s', type)
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
|
|
484
|
+
runModelHook = async (model, hookName, ...args) => {
|
|
485
485
|
const { orderBy } = this.app.lib._
|
|
486
486
|
const hooks = orderBy(model.hooks.filter(hook => hook.name === hookName), ['level'])
|
|
487
487
|
for (const hook of hooks) {
|
package/lib/collect-models.js
CHANGED
|
@@ -203,7 +203,9 @@ export async function sanitizeAll (model) {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
await runHook(`dobo.${camelCase(model.name)}:afterSanitizeModel`, model)
|
|
206
|
-
//
|
|
206
|
+
// fields that can participate in table scan if needed
|
|
207
|
+
model.scans = []
|
|
208
|
+
// sorting only possible using these fields
|
|
207
209
|
model.sortables = []
|
|
208
210
|
for (const index of model.indexes) {
|
|
209
211
|
model.sortables.push(...index.fields)
|
package/lib/factory/driver.js
CHANGED
|
@@ -13,10 +13,11 @@ export async function execHook (name, ...args) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export async function execModelHook (name, ...args) {
|
|
16
|
+
if (['beforeBuildQuery', 'beforeBuildSearch', 'afterBuildQuery', 'afterBuildSearch'].includes(name)) return
|
|
16
17
|
const { last } = this.app.lib._
|
|
17
|
-
const {
|
|
18
|
+
const { runModelHook } = this.app.dobo
|
|
18
19
|
const { noModelHook } = last(args)
|
|
19
|
-
if (!noModelHook) await
|
|
20
|
+
if (!noModelHook) await runModelHook(this, name, ...args)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export async function execDynHook (name, ...args) {
|
|
@@ -51,6 +52,7 @@ export async function execValidation (body, options = {}) {
|
|
|
51
52
|
*/
|
|
52
53
|
export async function getFilterAndOptions (filter = {}, options = {}, action) {
|
|
53
54
|
const { cloneDeep, omit } = this.app.lib._
|
|
55
|
+
const { runModelHook } = this.app.dobo
|
|
54
56
|
const keys = ['req', 'reply']
|
|
55
57
|
const nFilter = cloneDeep(omit(filter, keys))
|
|
56
58
|
const nOptions = cloneDeep(omit(options, keys))
|
|
@@ -62,9 +64,13 @@ export async function getFilterAndOptions (filter = {}, options = {}, action) {
|
|
|
62
64
|
nOptions[key] = options[key]
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
nFilter.
|
|
67
|
-
|
|
67
|
+
if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuildQuery', nFilter.query, nOptions)
|
|
68
|
+
nFilter.query = buildFilterQuery.call(this, nFilter) ?? {}
|
|
69
|
+
if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildQuery', nFilter.query, nOptions)
|
|
70
|
+
if (!nOptions.noModelHook) await runModelHook(this, 'beforeBuilSearch', nFilter.search, nOptions)
|
|
71
|
+
nFilter.search = buildFilterSearch.call(this, nFilter) ?? {}
|
|
72
|
+
if (!nOptions.noModelHook) await runModelHook(this, 'afterBuildSearch', nFilter.search, nOptions)
|
|
73
|
+
if (this.driver.idField.name !== 'id') replaceIdInQuerySearch.call(this, nFilter)
|
|
68
74
|
const { limit, page, skip, sort } = preparePagination.call(this, nFilter, nOptions)
|
|
69
75
|
nFilter.limit = limit
|
|
70
76
|
nFilter.page = page
|
|
@@ -218,37 +224,42 @@ export async function getMultiRefs (records = [], options = {}) {
|
|
|
218
224
|
}
|
|
219
225
|
}
|
|
220
226
|
|
|
221
|
-
export function buildFilterQuery (filter = {}
|
|
227
|
+
export function buildFilterQuery (filter = {}) {
|
|
222
228
|
const { trim, find, isString, isPlainObject } = this.app.lib._
|
|
223
|
-
let query = {}
|
|
224
|
-
|
|
229
|
+
let query = filter.query ?? {}
|
|
230
|
+
let q = {}
|
|
231
|
+
if (isString(query)) {
|
|
225
232
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
else if (
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
query = trim(query)
|
|
234
|
+
if (query.startsWith('{')) q = JSON.parse(query) // JSON formatted query
|
|
235
|
+
else if (query.includes(':')) q = nql(query).parse() // NQL
|
|
236
|
+
else if (this.driver.support.search) {
|
|
237
|
+
filter.search = q
|
|
238
|
+
return {} //
|
|
239
|
+
} else {
|
|
240
|
+
let scans = [...this.scans]
|
|
241
|
+
if (scans.length === 0) scans = [...this.sortables]
|
|
242
|
+
const fields = scans.filter(f => {
|
|
232
243
|
const field = find(this.properties, { name: f, type: 'string' })
|
|
233
244
|
return !!field
|
|
234
245
|
})
|
|
235
246
|
const parts = fields.map(f => {
|
|
236
|
-
if (
|
|
237
|
-
if (
|
|
238
|
-
return `${f}:~'${
|
|
247
|
+
if (query[0] === '*') return `${f}:~$'${query.replaceAll('*', '')}'`
|
|
248
|
+
if (query[query.length - 1] === '*') return `${f}:~^'${query.replaceAll('*', '')}'`
|
|
249
|
+
return `${f}:~'${query.replaceAll('*', '')}'`
|
|
239
250
|
})
|
|
240
|
-
if (parts.length === 1)
|
|
241
|
-
else if (parts.length > 1)
|
|
251
|
+
if (parts.length === 1) q = nql(parts[0]).parse()
|
|
252
|
+
else if (parts.length > 1) q = nql(parts.join(',')).parse()
|
|
242
253
|
}
|
|
243
254
|
} catch (err) {
|
|
244
|
-
this.
|
|
255
|
+
this.plugin.error('invalidQuery', { orgMessage: err.message })
|
|
245
256
|
}
|
|
246
|
-
} else if (isPlainObject(
|
|
247
|
-
return sanitizeQuery.call(this,
|
|
257
|
+
} else if (isPlainObject(query)) q = query
|
|
258
|
+
return sanitizeQuery.call(this, q)
|
|
248
259
|
}
|
|
249
260
|
|
|
250
261
|
function sanitizeQuery (query = {}, parent) {
|
|
251
|
-
const {
|
|
262
|
+
const { isPlainObject, isArray, find, cloneDeep } = this.app.lib._
|
|
252
263
|
const { isSet } = this.app.lib.aneka
|
|
253
264
|
const { dayjs } = this.app.lib
|
|
254
265
|
const obj = cloneDeep(query)
|
|
@@ -256,6 +267,7 @@ function sanitizeQuery (query = {}, parent) {
|
|
|
256
267
|
|
|
257
268
|
const sanitizeField = (prop, val) => {
|
|
258
269
|
if (!prop) return val
|
|
270
|
+
if (val instanceof RegExp) return val
|
|
259
271
|
if (['datetime'].includes(prop.type)) {
|
|
260
272
|
const dt = dayjs(val)
|
|
261
273
|
return dt.isValid() ? dt.toDate() : val
|
|
@@ -287,9 +299,10 @@ function sanitizeQuery (query = {}, parent) {
|
|
|
287
299
|
return obj
|
|
288
300
|
}
|
|
289
301
|
|
|
290
|
-
export function
|
|
302
|
+
export function buildFilterSearch (filter = {}) {
|
|
291
303
|
const { isPlainObject, trim, has, uniq } = this.app.lib._
|
|
292
|
-
|
|
304
|
+
const search = filter.search ?? {}
|
|
305
|
+
let input = search
|
|
293
306
|
if (isPlainObject(input)) return input
|
|
294
307
|
const split = (value) => {
|
|
295
308
|
let [field, val] = value.split(':').map(i => i.trim())
|
|
@@ -313,37 +326,37 @@ export function buildFilterMatch (filter = {}, options = {}) {
|
|
|
313
326
|
items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
|
|
314
327
|
}
|
|
315
328
|
}
|
|
316
|
-
const
|
|
329
|
+
const s = {}
|
|
317
330
|
for (const index of this.indexes.filter(i => i.type === 'fulltext')) {
|
|
318
331
|
for (const f of index.fields) {
|
|
319
332
|
const value = []
|
|
320
333
|
if (typeof items[f] === 'string') items[f] = [items[f]]
|
|
321
334
|
if (has(items, f)) value.push(...items[f])
|
|
322
|
-
if (!
|
|
323
|
-
|
|
335
|
+
if (!s[f]) s[f] = []
|
|
336
|
+
s[f] = uniq([...s[f], ...value])
|
|
324
337
|
}
|
|
325
338
|
}
|
|
326
|
-
if (has(items, '*'))
|
|
327
|
-
return
|
|
339
|
+
if (has(items, '*')) s['*'] = items['*']
|
|
340
|
+
return s
|
|
328
341
|
}
|
|
329
342
|
|
|
330
|
-
export function
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
export function replaceIdInQuerySearch (filter) {
|
|
344
|
+
// query
|
|
345
|
+
const query = JSON.stringify(filter.query ?? {}, (key, value) => {
|
|
346
|
+
if (value instanceof RegExp) return ['__REGEXP__', value.source, value.flags]
|
|
347
|
+
return value
|
|
348
|
+
}).replaceAll('"id"', `"${this.driver.idField.name}"`)
|
|
349
|
+
try {
|
|
350
|
+
filter.query = JSON.parse(query, (key, value) => {
|
|
351
|
+
if (Array.isArray(value) && value[0] === '__REGEXP__') return new RegExp(value[1], value[2])
|
|
334
352
|
return value
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const match = JSON.stringify(filter.match ?? {}).replaceAll('"id"', `"${this.driver.idField.name}"`)
|
|
343
|
-
try {
|
|
344
|
-
filter.match = JSON.parse(match)
|
|
345
|
-
} catch (err) {}
|
|
346
|
-
}
|
|
353
|
+
})
|
|
354
|
+
} catch (err) {}
|
|
355
|
+
// search
|
|
356
|
+
const search = JSON.stringify(filter.search ?? {}).replaceAll('"id"', `"${this.driver.idField.name}"`)
|
|
357
|
+
try {
|
|
358
|
+
filter.search = JSON.parse(search)
|
|
359
|
+
} catch (err) {}
|
|
347
360
|
}
|
|
348
361
|
|
|
349
362
|
/**
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-02-01
|
|
4
|
+
|
|
5
|
+
- [2.6.0] Add ```model.scans``` for fields that can participate in table scans if necessary
|
|
6
|
+
- [2.6.0] Add ```driver.support.search``` for driver's fulltext search support
|
|
7
|
+
- [2.6.0] Add model hooks ```before/afterBuildQuery/Search```
|
|
8
|
+
|
|
3
9
|
## 2026-01-30
|
|
4
10
|
|
|
5
11
|
- [2.5.0] Add feature to push custom ```options._data```. If provided, this will be used instead of auto generated one.
|