dobo 1.1.0 → 1.1.2

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.
Files changed (75) hide show
  1. package/bajo/intl/en-US.json +5 -1
  2. package/bajo/intl/id.json +0 -1
  3. package/bajoCli/applet/connection.js +1 -1
  4. package/bajoCli/applet/lib/post-process.js +1 -1
  5. package/bajoCli/applet/model-clear.js +1 -1
  6. package/bajoCli/applet/model-rebuild.js +27 -7
  7. package/bajoCli/applet/record-create.js +1 -1
  8. package/bajoCli/applet/record-find.js +1 -1
  9. package/bajoCli/applet/record-get.js +1 -1
  10. package/bajoCli/applet/record-remove.js +1 -1
  11. package/bajoCli/applet/record-update.js +1 -1
  12. package/bajoCli/applet/schema.js +1 -1
  13. package/bajoCli/applet/stat-count.js +1 -1
  14. package/dobo/feature/removed-at.js +85 -0
  15. package/lib/add-fixtures.js +1 -1
  16. package/lib/build-bulk-action.js +1 -1
  17. package/lib/check-unique.js +5 -5
  18. package/lib/collect-connections.js +1 -1
  19. package/lib/collect-drivers.js +1 -1
  20. package/lib/collect-feature.js +1 -1
  21. package/lib/collect-schemas.js +3 -3
  22. package/lib/exec-feature-hook.js +4 -3
  23. package/lib/exec-validation.js +1 -1
  24. package/lib/generic-prop-sanitizer.js +1 -1
  25. package/lib/handle-attachment-upload.js +1 -1
  26. package/lib/mem-db/conn-sanitizer.js +1 -1
  27. package/lib/mem-db/instantiate.js +2 -2
  28. package/lib/mem-db/method/record/find.js +1 -1
  29. package/lib/mem-db/method/record/get.js +1 -1
  30. package/lib/mem-db/method/record/remove.js +1 -1
  31. package/lib/mem-db/method/record/update.js +1 -1
  32. package/lib/mem-db/start.js +1 -1
  33. package/lib/merge-attachment-info.js +2 -2
  34. package/lib/resolve-method.js +2 -2
  35. package/lib/sanitize-schema.js +3 -3
  36. package/package.json +1 -1
  37. package/plugin/factory.js +324 -0
  38. package/plugin/method/attachment/copy-uploaded.js +1 -1
  39. package/plugin/method/attachment/create.js +1 -1
  40. package/plugin/method/attachment/find.js +1 -1
  41. package/plugin/method/attachment/get-path.js +1 -1
  42. package/plugin/method/attachment/get.js +1 -1
  43. package/plugin/method/attachment/remove.js +1 -1
  44. package/plugin/method/bulk/create.js +1 -1
  45. package/plugin/method/model/clear.js +1 -1
  46. package/plugin/method/model/create.js +1 -1
  47. package/plugin/method/model/drop.js +1 -1
  48. package/plugin/method/model/exists.js +1 -1
  49. package/plugin/method/record/clear.js +1 -1
  50. package/plugin/method/record/count.js +13 -8
  51. package/plugin/method/record/create.js +9 -8
  52. package/plugin/method/record/find-one.js +11 -4
  53. package/plugin/method/record/find.js +10 -4
  54. package/plugin/method/record/get.js +10 -4
  55. package/plugin/method/record/remove.js +9 -4
  56. package/plugin/method/record/update.js +6 -4
  57. package/plugin/method/record/upsert.js +20 -6
  58. package/plugin/method/sanitize/body.js +1 -1
  59. package/plugin/method/sanitize/date.js +1 -1
  60. package/plugin/method/validate.js +2 -2
  61. package/waibuMpa/route/attachment/@model/@id/@field/@file.js +9 -2
  62. package/plugin/.alias +0 -1
  63. package/plugin/config.json +0 -36
  64. package/plugin/init.js +0 -29
  65. package/plugin/method/aggregate-types.js +0 -1
  66. package/plugin/method/build-match.js +0 -34
  67. package/plugin/method/build-query.js +0 -14
  68. package/plugin/method/get-connection.js +0 -6
  69. package/plugin/method/get-info.js +0 -14
  70. package/plugin/method/get-schema.js +0 -10
  71. package/plugin/method/pick-record.js +0 -36
  72. package/plugin/method/prep-pagination.js +0 -63
  73. package/plugin/method/prop-type.js +0 -43
  74. package/plugin/method/validation-error-message.js +0 -12
  75. package/plugin/start.js +0 -20
@@ -0,0 +1,324 @@
1
+ import collectConnections from '../lib/collect-connections.js'
2
+ import collectDrivers from '../lib/collect-drivers.js'
3
+ import collectFeature from '../lib/collect-feature.js'
4
+ import collectSchemas from '../lib/collect-schemas.js'
5
+ import memDbStart from '../lib/mem-db/start.js'
6
+ import memDbInstantiate from '../lib/mem-db/instantiate.js'
7
+ import nql from '@tryghost/nql'
8
+
9
+ async function factory (pkgName) {
10
+ const me = this
11
+
12
+ return class Dobo extends this.lib.BajoPlugin {
13
+ constructor () {
14
+ super(pkgName, me.app)
15
+ this.alias = 'db'
16
+ this.config = {
17
+ connections: [],
18
+ mergeProps: ['connections'],
19
+ validationParams: {
20
+ abortEarly: false,
21
+ convert: false,
22
+ allowUnknown: true
23
+ },
24
+ default: {
25
+ property: {
26
+ text: {
27
+ kind: 'text'
28
+ },
29
+ string: {
30
+ length: 50
31
+ }
32
+ },
33
+ filter: {
34
+ limit: 25,
35
+ maxLimit: 200,
36
+ sort: ['dt:-1', 'updatedAt:-1', 'updated_at:-1', 'createdAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
37
+ },
38
+ idField: {
39
+ type: 'string',
40
+ maxLength: 50,
41
+ required: true,
42
+ index: { type: 'primary' }
43
+ }
44
+ },
45
+ memDb: {
46
+ createDefConnAtStart: true,
47
+ persistence: {
48
+ syncPeriod: 1
49
+ }
50
+ }
51
+ }
52
+ this.aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
53
+ this.propType = {
54
+ integer: {
55
+ validator: 'number'
56
+ },
57
+ smallint: {
58
+ validator: 'number'
59
+ },
60
+ text: {
61
+ validator: 'string',
62
+ kind: 'text',
63
+ choices: ['text', 'mediumtext', 'longtext']
64
+ },
65
+ string: {
66
+ validator: 'string',
67
+ maxLength: 255,
68
+ minLength: 0
69
+ },
70
+ float: {
71
+ validator: 'number'
72
+ },
73
+ double: {
74
+ validator: 'number'
75
+ },
76
+ boolean: {
77
+ validator: 'boolean'
78
+ },
79
+ date: {
80
+ validator: 'date'
81
+ },
82
+ datetime: {
83
+ validator: 'date'
84
+ },
85
+ time: {
86
+ validator: 'date'
87
+ },
88
+ timestamp: {
89
+ validator: 'timestamp'
90
+ },
91
+ object: {},
92
+ array: {}
93
+ }
94
+ }
95
+
96
+ init = async () => {
97
+ const { buildCollections } = this.app.bajo
98
+ const { fs } = this.lib
99
+ const checkType = async (item, items) => {
100
+ const { filter } = this.lib._
101
+ const existing = filter(items, { type: 'dobo:memory' })
102
+ if (existing.length > 1) this.fatal('onlyOneConnType%s', item.type)
103
+ }
104
+
105
+ fs.ensureDirSync(`${this.dir.data}/attachment`)
106
+ await collectDrivers.call(this)
107
+ if (this.config.memDb.createDefConnAtStart) {
108
+ this.config.connections.push({
109
+ type: 'dobo:memory',
110
+ name: 'memory'
111
+ })
112
+ }
113
+ this.connections = await buildCollections({ ns: this.name, container: 'connections', handler: collectConnections, dupChecks: ['name', checkType] })
114
+ if (this.connections.length === 0) this.log.warn('notFound%s', this.print.write('connection'))
115
+ await collectFeature.call(this)
116
+ await collectSchemas.call(this)
117
+ }
118
+
119
+ start = async (conns = 'all', noRebuild = true) => {
120
+ const { importModule, breakNsPath } = this.app.bajo
121
+ const { find, filter, isString, map } = this.lib._
122
+ if (conns === 'all') conns = this.connections
123
+ else if (isString(conns)) conns = filter(this.connections, { name: conns })
124
+ else conns = map(conns, c => find(this.connections, { name: c }))
125
+ for (const c of conns) {
126
+ const { ns } = breakNsPath(c.type)
127
+ const schemas = filter(this.schemas, { connection: c.name })
128
+ const mod = c.type === 'dobo:memory' ? memDbInstantiate : await importModule(`${ns}:/${this.name}/boot/instantiate.js`)
129
+ await mod.call(this.app[ns], { connection: c, noRebuild, schemas })
130
+ this.log.trace('driverInstantiated%s%s', c.driver, c.name)
131
+ }
132
+ await memDbStart.call(this)
133
+ }
134
+
135
+ pickRecord = async ({ record, fields, schema = {}, hidden = [], forceNoHidden } = {}) => {
136
+ const { isArray, pick, clone, isEmpty, omit } = this.lib._
137
+ const { dayjs } = this.lib
138
+
139
+ const transform = async ({ record, schema, hidden = [], forceNoHidden } = {}) => {
140
+ if (record._id) {
141
+ record.id = record._id
142
+ delete record._id
143
+ }
144
+ const defHidden = [...schema.hidden, ...hidden]
145
+ let result = {}
146
+ for (const p of schema.properties) {
147
+ if (!forceNoHidden && defHidden.includes(p.name)) continue
148
+ result[p.name] = record[p.name] ?? null
149
+ if (record[p.name] === null) continue
150
+ switch (p.type) {
151
+ case 'boolean': result[p.name] = !!result[p.name]; break
152
+ case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
153
+ case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
154
+ case 'datetime': result[p.name] = dayjs(record[p.name]).toISOString(); break
155
+ }
156
+ }
157
+ result = await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
158
+ if (record._rel) result._rel = record._rel
159
+ return result
160
+ }
161
+
162
+ if (isEmpty(record)) return record
163
+ if (hidden.length > 0) record = omit(record, hidden)
164
+ if (!isArray(fields)) return await transform.call(this, { record, schema, hidden, forceNoHidden })
165
+ const fl = clone(fields)
166
+ if (!fl.includes('id')) fl.unshift('id')
167
+ if (record._rel) fl.push('_rel')
168
+ return pick(await transform.call(this, { record, schema, hidden, forceNoHidden }), fl)
169
+ }
170
+
171
+ prepPagination = async (filter = {}, schema, options = {}) => {
172
+ const buildPageSkipLimit = (filter) => {
173
+ let limit = parseInt(filter.limit) || this.config.default.filter.limit
174
+ if (limit === -1) limit = this.config.default.filter.maxLimit
175
+ if (limit > this.config.default.filter.maxLimit) limit = this.config.default.filter.maxLimit
176
+ if (limit < 1) limit = 1
177
+ let page = parseInt(filter.page) || 1
178
+ if (page < 1) page = 1
179
+ let skip = (page - 1) * limit
180
+ if (filter.skip) {
181
+ skip = parseInt(filter.skip) || skip
182
+ page = undefined
183
+ }
184
+ if (skip < 0) skip = 0
185
+ return { page, skip, limit }
186
+ }
187
+
188
+ const buildSort = (input, schema, allowSortUnindexed) => {
189
+ const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.lib._
190
+ let sort
191
+ if (schema && isEmpty(input)) {
192
+ const columns = map(schema.properties, 'name')
193
+ each(this.config.default.filter.sort, s => {
194
+ const [col] = s.split(':')
195
+ if (columns.includes(col)) {
196
+ input = s
197
+ return false
198
+ }
199
+ })
200
+ }
201
+ if (!isEmpty(input)) {
202
+ if (isPlainObject(input)) sort = input
203
+ else if (isString(input)) {
204
+ const item = {}
205
+ each(input.split('+'), text => {
206
+ let [col, dir] = map(trim(text).split(':'), i => trim(i))
207
+ dir = (dir ?? '').toUpperCase()
208
+ dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
209
+ item[col] = dir / Math.abs(dir)
210
+ })
211
+ sort = item
212
+ }
213
+ if (schema) {
214
+ const items = keys(sort)
215
+ each(items, i => {
216
+ if (!schema.sortables.includes(i) && !allowSortUnindexed) throw this.error('sortOnUnindexedField%s%s', i, schema.name)
217
+ // if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
218
+ })
219
+ }
220
+ }
221
+ return sort
222
+ }
223
+
224
+ const { page, skip, limit } = buildPageSkipLimit.call(this, filter)
225
+ let sortInput = filter.sort
226
+ try {
227
+ sortInput = JSON.parse(sortInput)
228
+ } catch (err) {}
229
+ const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
230
+ return { limit, page, skip, sort }
231
+ }
232
+
233
+ buildMatch = ({ input = '', schema, options }) => {
234
+ const { isPlainObject, trim } = this.lib._
235
+ const split = (value, schema) => {
236
+ let [field, val] = value.split(':').map(i => i.trim())
237
+ if (!val) {
238
+ val = field
239
+ field = '*'
240
+ }
241
+ return { field, value: val }
242
+ }
243
+
244
+ input = trim(input)
245
+ let items = {}
246
+ if (isPlainObject(input)) items = input
247
+ else if (input[0] === '{') items = JSON.parse(input)
248
+ else {
249
+ for (const item of input.split('+').map(i => i.trim())) {
250
+ const part = split(item, schema)
251
+ if (!items[part.field]) items[part.field] = []
252
+ items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
253
+ }
254
+ }
255
+ const matcher = {}
256
+ for (const f of schema.fullText.fields) {
257
+ const value = []
258
+ if (typeof items[f] === 'string') items[f] = [items[f]]
259
+ if (Object.prototype.hasOwnProperty.call(items, f)) value.push(...items[f])
260
+ matcher[f] = value
261
+ }
262
+ if (Object.prototype.hasOwnProperty.call(items, '*')) matcher['*'] = items['*']
263
+ return matcher
264
+ }
265
+
266
+ buildQuery = async ({ filter, schema, options = {} } = {}) => {
267
+ const { trim, isString, isPlainObject } = this.lib._
268
+ let query = {}
269
+ if (isString(filter.query)) {
270
+ filter.oquery = filter.query
271
+ if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
272
+ else query = nql(filter.query).parse()
273
+ } else if (isPlainObject(filter.query)) query = filter.query
274
+ return query
275
+ }
276
+
277
+ validationErrorMessage = (err) => {
278
+ let text = err.message
279
+ if (err.details) {
280
+ text += ' -> '
281
+ text += this.app.bajo.join(err.details.map((d, idx) => {
282
+ return `${d.field}@${err.model}: ${d.error} (${d.value})`
283
+ }))
284
+ }
285
+ return text
286
+ }
287
+
288
+ getConnection = (name) => {
289
+ const { find } = this.lib._
290
+ return find(this.connections, { name })
291
+ }
292
+
293
+ getInfo = (name) => {
294
+ const { breakNsPath } = this.app.bajo
295
+ const { find, map } = this.lib._
296
+ const schema = this.getSchema(name)
297
+ const conn = this.getConnection(schema.connection)
298
+ const { ns, path: type } = breakNsPath(conn.type)
299
+ const driver = find(this.drivers, { type, ns, driver: conn.driver })
300
+ const instance = find(this.app[driver.ns].instances, { name: schema.connection })
301
+ const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
302
+ const returning = [map(schema.properties, 'name'), opts]
303
+ return { instance, driver, connection: conn, returning, schema }
304
+ }
305
+
306
+ getSchema = (input, cloned = true) => {
307
+ const { find, isPlainObject, cloneDeep } = this.lib._
308
+ let name = isPlainObject(input) ? input.name : input
309
+ name = this.app.bajo.pascalCase(name)
310
+ const schema = find(this.schemas, { name })
311
+ if (!schema) throw this.error('unknownModelSchema%s', name)
312
+ return cloned ? cloneDeep(schema) : schema
313
+ }
314
+
315
+ getMemdbStorage = (name, fields = []) => {
316
+ const { map, pick } = this.lib._
317
+ const all = this.memDb.storage[name] ?? []
318
+ if (fields.length === 0) return all
319
+ return map(all, item => pick(item, fields))
320
+ }
321
+ }
322
+ }
323
+
324
+ export default factory
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
 
3
3
  async function copyUploaded (name, id, options = {}) {
4
- const { fs } = this.app.bajo.lib
4
+ const { fs } = this.lib
5
5
  const { req, setField, setFile, mimeType, stats, silent = true } = options
6
6
  name = this.attachmentPreCheck(name)
7
7
  if (!name) {
@@ -1,7 +1,7 @@
1
1
  import mergeAttachmentInfo from '../../../lib/merge-attachment-info.js'
2
2
 
3
3
  async function create (name, id, options = {}) {
4
- const { fs } = this.app.bajo.lib
4
+ const { fs } = this.lib
5
5
  name = this.attachmentPreCheck(name)
6
6
  if (!name) return
7
7
  const { source, field, file } = options
@@ -1,7 +1,7 @@
1
1
  import mergeAttachmentInfo from '../../../lib/merge-attachment-info.js'
2
2
 
3
3
  async function find (name, id, options = {}) {
4
- const { fastGlob, fs } = this.app.bajo.lib
4
+ const { fastGlob, fs } = this.lib
5
5
  const { getPluginDataDir } = this.app.bajo
6
6
  name = this.attachmentPreCheck(name)
7
7
  if (!name) return
@@ -1,6 +1,6 @@
1
1
  async function getPath (name, id, field, file, options = {}) {
2
2
  const { pascalCase, getPluginDataDir } = this.app.bajo
3
- const { fs } = this.app.bajo.lib
3
+ const { fs } = this.lib
4
4
  const dir = `${getPluginDataDir(this.name)}/attachment/${pascalCase(name)}/${id}`
5
5
  if (options.dirOnly) return dir
6
6
  const path = field ? `${dir}/${field}/${file}` : `${dir}/${file}`
@@ -1,7 +1,7 @@
1
1
  async function get (name, id, field, file, options = {}) {
2
2
  name = this.attachmentPreCheck(name)
3
3
  if (!name) return
4
- const { find } = this.app.bajo.lib._
4
+ const { find } = this.lib._
5
5
  const all = await this.attachmentFind(name, id, options)
6
6
  if (field === 'null') field = null
7
7
  const data = find(all, { field, file })
@@ -1,5 +1,5 @@
1
1
  async function remove (name, id, field, file, options = {}) {
2
- const { fs } = this.app.bajo.lib
2
+ const { fs } = this.lib
3
3
  name = this.attachmentPreCheck(name)
4
4
  if (!name) return
5
5
  const path = await this.attachmentGetPath(name, id, field, file)
@@ -5,7 +5,7 @@ import execFeatureHook from '../../../lib/exec-feature-hook.js'
5
5
  async function create (name, inputs, options) {
6
6
  const { generateId, runHook, isSet } = this.app.bajo
7
7
  const { clearModel } = this.cache ?? {}
8
- const { find } = this.app.bajo.lib._
8
+ const { find } = this.lib._
9
9
  options.dataOnly = options.dataOnly ?? true
10
10
  options.truncateString = options.truncateString ?? true
11
11
  const { noHook, noValidation } = options
@@ -2,7 +2,7 @@ import resolveMethod from '../../../lib/resolve-method.js'
2
2
 
3
3
  async function clear (name, options = {}) {
4
4
  const { runHook } = this.app.bajo
5
- const { camelCase } = this.app.bajo.lib._
5
+ const { camelCase } = this.lib._
6
6
 
7
7
  await this.modelExists(name, true)
8
8
  const { noHook } = options
@@ -2,7 +2,7 @@ import resolveMethod from '../../../lib/resolve-method.js'
2
2
 
3
3
  async function create (name, options = {}) {
4
4
  const { runHook } = this.app.bajo
5
- const { camelCase } = this.app.bajo.lib._
5
+ const { camelCase } = this.lib._
6
6
 
7
7
  const { handler, schema } = await resolveMethod.call(this, name, 'model-create', options)
8
8
  if (!options.noHook) {
@@ -2,7 +2,7 @@ import resolveMethod from '../../../lib/resolve-method.js'
2
2
 
3
3
  async function drop (name, options = {}) {
4
4
  const { runHook } = this.app.bajo
5
- const { camelCase } = this.app.bajo.lib._
5
+ const { camelCase } = this.lib._
6
6
  const { handler, schema } = await resolveMethod.call(this, name, 'model-drop', options)
7
7
 
8
8
  if (!options.noHook) {
@@ -5,7 +5,7 @@ const cache = {}
5
5
  async function exists (name, thrown, options = {}) {
6
6
  if (cache[name]) return cache[name]
7
7
  const { runHook } = this.app.bajo
8
- const { camelCase } = this.app.bajo.lib._
8
+ const { camelCase } = this.lib._
9
9
  const { handler, schema } = await resolveMethod.call(this, name, 'model-exists', options)
10
10
  if (!options.noHook) {
11
11
  await runHook(`${this.name}:beforeModelExists`, schema, options)
@@ -3,7 +3,7 @@ import resolveMethod from '../../../lib/resolve-method.js'
3
3
  async function clear (name, opts = {}) {
4
4
  const { runHook } = this.app.bajo
5
5
  await this.modelExists(name, true)
6
- const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
6
+ const { cloneDeep, camelCase, omit } = this.lib._
7
7
  const options = cloneDeep(omit(opts, ['req', 'reply']))
8
8
  options.req = opts.req
9
9
  options.reply = opts.reply
@@ -1,14 +1,16 @@
1
1
  import resolveMethod from '../../../lib/resolve-method.js'
2
+ import execFeatureHook from '../../../lib/exec-feature-hook.js'
2
3
 
3
4
  async function count (name, filter = {}, opts = {}) {
4
5
  const { runHook } = this.app.bajo
5
6
  const { get, set } = this.cache ?? {}
6
- const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
7
+ const { cloneDeep, camelCase, omit } = this.lib._
8
+ delete opts.record
7
9
  const options = cloneDeep(omit(opts, ['req', 'reply']))
8
10
  options.req = opts.req
9
11
  options.reply = opts.reply
10
12
  options.dataOnly = options.dataOnly ?? true
11
- let { dataOnly, noHook, noCache } = options
13
+ let { dataOnly, noHook, noCache, noFeatureHook } = options
12
14
  options.dataOnly = false
13
15
  await this.modelExists(name, true)
14
16
  const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-count', options)
@@ -20,20 +22,23 @@ async function count (name, filter = {}, opts = {}) {
20
22
  await runHook(`${this.name}:beforeRecordCount`, name, filter, options)
21
23
  await runHook(`${this.name}.${camelCase(name)}:beforeRecordCount`, filter, options)
22
24
  }
23
- if (get && !noCache) {
25
+ if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCount', { schema, filter, options })
26
+ if (get && !noCache && !options.record) {
24
27
  const cachedResult = await get({ model: name, filter, options })
25
28
  if (cachedResult) {
26
29
  cachedResult.cached = true
27
30
  return dataOnly ? cachedResult.data : cachedResult
28
31
  }
29
32
  }
30
- const count = await handler.call(this.app[driver.ns], { schema, filter, options })
33
+ const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
34
+ delete options.record
31
35
  if (!noHook) {
32
- await runHook(`${this.name}.${camelCase(name)}:afterRecordCount`, filter, options, count)
33
- await runHook(`${this.name}:afterRecordCount`, name, filter, options, count)
36
+ await runHook(`${this.name}.${camelCase(name)}:afterRecordCount`, filter, options, record)
37
+ await runHook(`${this.name}:afterRecordCount`, name, filter, options, record)
34
38
  }
35
- if (set && !noCache) await set({ model: name, filter, options, count })
36
- return dataOnly ? count.data : count
39
+ if (set && !noCache) await set({ model: name, filter, options, record })
40
+ if (!noFeatureHook) await execFeatureHook.call(this, 'afterCount', { schema, filter, options, record })
41
+ return dataOnly ? record.data : record
37
42
  }
38
43
 
39
44
  export default count
@@ -9,7 +9,8 @@ import singleRelRows from '../../../lib/single-rel-rows.js'
9
9
  async function create (name, input, opts = {}) {
10
10
  const { generateId, runHook, isSet } = this.app.bajo
11
11
  const { clearModel } = this.cache ?? {}
12
- const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.app.bajo.lib._
12
+ const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.lib._
13
+ delete opts.record
13
14
  const options = cloneDeep(omit(opts, ['req', 'reply']))
14
15
  options.req = opts.req
15
16
  options.reply = opts.reply
@@ -37,10 +38,8 @@ async function create (name, input, opts = {}) {
37
38
  }
38
39
  } else if (['integer', 'smallint'].includes(idField.type) && !idField.autoInc) input.id = generateId('int')
39
40
  }
40
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body })
41
41
  if (!noValidation) body = await execValidation.call(this, { name, body, options })
42
42
  if (isSet(body.id) && !noCheckUnique) await checkUnique.call(this, { schema, body })
43
- let record = {}
44
43
  const nbody = {}
45
44
  forOwn(body, (v, k) => {
46
45
  if (v === undefined) return undefined
@@ -49,20 +48,22 @@ async function create (name, input, opts = {}) {
49
48
  if (options.truncateString && isSet(v) && ['string', 'text'].includes(prop.type)) v = v.slice(0, prop.maxLength)
50
49
  nbody[k] = v
51
50
  })
52
- record = await handler.call(this.app[driver.ns], { schema, body: nbody, options })
51
+ if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body: nbody, options })
52
+ const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, body: nbody, options }))
53
+ delete options.record
53
54
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
54
55
  if (options.req) {
55
56
  if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id: body.id, body, options, action: 'create' })
56
57
  if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordCreated'))
57
58
  }
58
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body, record })
59
59
  if (!noHook) {
60
- await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, body, options, record)
61
- await runHook(`${this.name}:afterRecordCreate`, name, body, options, record)
60
+ await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
61
+ await runHook(`${this.name}:afterRecordCreate`, name, nbody, options, record)
62
62
  }
63
- if (clearModel) await clearModel({ model: name, body, options, record })
63
+ if (clearModel) await clearModel({ model: name, body: nbody, options, record })
64
64
  if (noResult) return
65
65
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
66
+ if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body: nbody, options, record })
66
67
  return dataOnly ? record.data : record
67
68
  }
68
69
 
@@ -1,15 +1,17 @@
1
1
  import resolveMethod from '../../../lib/resolve-method.js'
2
2
  import singleRelRows from '../../../lib/single-rel-rows.js'
3
+ import execFeatureHook from '../../../lib/exec-feature-hook.js'
3
4
 
4
5
  async function findOne (name, filter = {}, opts = {}) {
5
6
  const { runHook, isSet } = this.app.bajo
6
7
  const { get, set } = this.cache ?? {}
7
- const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
8
+ const { cloneDeep, camelCase, omit } = this.lib._
9
+ delete opts.record
8
10
  const options = cloneDeep(omit(opts, ['req', 'reply']))
9
11
  options.req = opts.req
10
12
  options.reply = opts.reply
11
13
  options.dataOnly = options.dataOnly ?? true
12
- let { fields, dataOnly, noHook, noCache, hidden, forceNoHidden } = options
14
+ let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
13
15
  options.count = false
14
16
  options.dataOnly = false
15
17
  await this.modelExists(name, true)
@@ -23,15 +25,19 @@ async function findOne (name, filter = {}, opts = {}) {
23
25
  await runHook(`${this.name}:beforeRecordFindOne`, name, filter, options)
24
26
  await runHook(`${this.name}.${camelCase(name)}:beforeRecordFindOne`, filter, options)
25
27
  }
26
- if (get && !noCache) {
28
+ if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFindOne', { schema, filter, options })
29
+ if (get && !noCache && !options.record) {
27
30
  const cachedResult = await get({ model: name, filter, options })
28
31
  if (cachedResult) {
29
32
  cachedResult.cached = true
30
33
  return dataOnly ? cachedResult.data : cachedResult
31
34
  }
32
35
  }
33
- const record = await handler.call(this.app[driver.ns], { schema, filter, options })
36
+ filter.limit = 1
37
+ const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
38
+ delete options.record
34
39
  record.data = record.data[0]
40
+
35
41
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
36
42
  if (!noHook) {
37
43
  await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
@@ -39,6 +45,7 @@ async function findOne (name, filter = {}, opts = {}) {
39
45
  }
40
46
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
41
47
  if (set && !noCache) await set({ model: name, filter, options, record })
48
+ if (!noFeatureHook) await execFeatureHook.call(this, 'afterFindOne', { schema, filter, options, record })
42
49
  return dataOnly ? record.data : record
43
50
  }
44
51
 
@@ -1,15 +1,17 @@
1
1
  import resolveMethod from '../../../lib/resolve-method.js'
2
2
  import multiRelRows from '../../../lib/multi-rel-rows.js'
3
+ import execFeatureHook from '../../../lib/exec-feature-hook.js'
3
4
 
4
5
  async function find (name, filter = {}, opts = {}) {
5
6
  const { runHook, isSet } = this.app.bajo
6
7
  const { get, set } = this.cache ?? {}
7
- const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
8
+ const { cloneDeep, camelCase, omit } = this.lib._
9
+ delete opts.records
8
10
  const options = cloneDeep(omit(opts, ['req', 'reply']))
9
11
  options.req = opts.req
10
12
  options.reply = opts.reply
11
13
  options.dataOnly = options.dataOnly ?? true
12
- let { fields, dataOnly, noHook, noCache, hidden, forceNoHidden } = options
14
+ let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
13
15
  options.count = options.count ?? false
14
16
  options.dataOnly = false
15
17
  await this.modelExists(name, true)
@@ -22,14 +24,17 @@ async function find (name, filter = {}, opts = {}) {
22
24
  await runHook(`${this.name}:beforeRecordFind`, name, filter, options)
23
25
  await runHook(`${this.name}.${camelCase(name)}:beforeRecordFind`, filter, options)
24
26
  }
25
- if (get && !noCache) {
27
+ if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFind', { schema, filter, options })
28
+ if (get && !noCache && !options.records) {
26
29
  const cachedResult = await get({ model: name, filter, options })
27
30
  if (cachedResult) {
28
31
  cachedResult.cached = true
32
+ if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records: cachedResult })
29
33
  return dataOnly ? cachedResult.data : cachedResult
30
34
  }
31
35
  }
32
- const records = await handler.call(this.app[driver.ns], { schema, filter, options })
36
+ const records = options.records ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
37
+ delete options.records
33
38
  if (isSet(options.rels)) await multiRelRows.call(this, { schema, records: records.data, options })
34
39
  if (!noHook) {
35
40
  await runHook(`${this.name}.${camelCase(name)}:afterRecordFind`, filter, options, records)
@@ -39,6 +44,7 @@ async function find (name, filter = {}, opts = {}) {
39
44
  records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
40
45
  }
41
46
  if (set && !noCache) await set({ model: name, filter, options, records })
47
+ if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
42
48
  return dataOnly ? records.data : records
43
49
  }
44
50