dobo 2.0.0 → 2.2.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.
Files changed (197) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +45 -0
  4. package/LICENSE +1 -1
  5. package/README.md +38 -19
  6. package/docs/Dobo.html +26 -0
  7. package/docs/data/search.json +1 -0
  8. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  9. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  10. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  11. package/docs/global.html +7 -0
  12. package/docs/index.html +3 -0
  13. package/docs/index.js.html +578 -0
  14. package/docs/lib_collect-connections.js.html +39 -0
  15. package/docs/lib_collect-drivers.js.html +52 -0
  16. package/docs/lib_collect-features.js.html +36 -0
  17. package/docs/lib_collect-schemas.js.html +94 -0
  18. package/docs/lib_index.js.html +6 -0
  19. package/docs/method_model_create.js.html +35 -0
  20. package/docs/method_model_drop.js.html +34 -0
  21. package/docs/method_model_exists.js.html +40 -0
  22. package/docs/method_record_count.js.html +69 -0
  23. package/docs/method_record_create.js.html +114 -0
  24. package/docs/method_record_find-all.js.html +44 -0
  25. package/docs/method_record_find-one.js.html +73 -0
  26. package/docs/method_record_find.js.html +118 -0
  27. package/docs/method_record_get.js.html +92 -0
  28. package/docs/method_record_remove.js.html +75 -0
  29. package/docs/method_record_update.js.html +107 -0
  30. package/docs/method_record_upsert.js.html +54 -0
  31. package/docs/method_sanitize_body.js.html +88 -0
  32. package/docs/method_sanitize_date.js.html +30 -0
  33. package/docs/method_sanitize_id.js.html +20 -0
  34. package/docs/method_validate.js.html +249 -0
  35. package/docs/module-Lib.html +3 -0
  36. package/docs/scripts/core.js +725 -0
  37. package/docs/scripts/core.min.js +23 -0
  38. package/docs/scripts/resize.js +90 -0
  39. package/docs/scripts/search.js +265 -0
  40. package/docs/scripts/search.min.js +6 -0
  41. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  42. package/docs/scripts/third-party/fuse.js +9 -0
  43. package/docs/scripts/third-party/hljs-line-num-original.js +366 -0
  44. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  45. package/docs/scripts/third-party/hljs-original.js +5164 -0
  46. package/docs/scripts/third-party/hljs.js +1 -0
  47. package/docs/scripts/third-party/popper.js +5 -0
  48. package/docs/scripts/third-party/tippy.js +1 -0
  49. package/docs/scripts/third-party/tocbot.js +671 -0
  50. package/docs/scripts/third-party/tocbot.min.js +1 -0
  51. package/docs/static/bitcoin.jpeg +0 -0
  52. package/docs/static/home.md +25 -0
  53. package/docs/static/logo-ecosystem.png +0 -0
  54. package/docs/static/logo.png +0 -0
  55. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  56. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  57. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  58. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  59. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  60. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  61. package/extend/bajo/intl/en-US.json +69 -30
  62. package/extend/bajo/intl/id.json +58 -29
  63. package/extend/bajoCli/applet/clear-record.js +22 -0
  64. package/extend/bajoCli/applet/connection.js +5 -5
  65. package/extend/bajoCli/applet/count-record.js +27 -0
  66. package/extend/bajoCli/applet/create-aggregate.js +33 -0
  67. package/extend/bajoCli/applet/create-histogram.js +33 -0
  68. package/extend/bajoCli/applet/create-record.js +39 -0
  69. package/extend/bajoCli/applet/find-record.js +27 -0
  70. package/extend/bajoCli/applet/get-record.js +27 -0
  71. package/extend/bajoCli/applet/lib/post-process.js +25 -26
  72. package/extend/bajoCli/applet/model.js +22 -0
  73. package/extend/bajoCli/applet/rebuild-model.js +91 -0
  74. package/extend/bajoCli/applet/remove-record.js +27 -0
  75. package/extend/bajoCli/applet/update-record.js +44 -0
  76. package/extend/bajoCli/applet.js +0 -0
  77. package/extend/dobo/driver/memory.js +170 -0
  78. package/extend/dobo/feature/created-at.js +10 -8
  79. package/extend/dobo/feature/dt.js +0 -0
  80. package/extend/dobo/feature/immutable.js +30 -0
  81. package/extend/dobo/feature/int-id.js +0 -0
  82. package/extend/dobo/feature/removed-at.js +35 -57
  83. package/extend/dobo/feature/updated-at.js +14 -12
  84. package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +5 -9
  85. package/extend/waibuStatic/virtual.json +0 -0
  86. package/index.js +420 -337
  87. package/lib/collect-connections.js +60 -21
  88. package/lib/collect-drivers.js +29 -35
  89. package/lib/collect-features.js +40 -0
  90. package/lib/collect-models.js +319 -0
  91. package/lib/factory/action.js +161 -0
  92. package/lib/factory/connection.js +62 -0
  93. package/lib/factory/driver.js +358 -0
  94. package/lib/factory/feature.js +33 -0
  95. package/lib/factory/model/_util.js +402 -0
  96. package/lib/factory/model/build.js +15 -0
  97. package/lib/factory/model/clear-record.js +17 -0
  98. package/lib/factory/model/count-record.js +17 -0
  99. package/lib/factory/model/create-aggregate.js +17 -0
  100. package/lib/factory/model/create-attachment.js +29 -0
  101. package/lib/factory/model/create-histogram.js +17 -0
  102. package/lib/factory/model/create-record.js +35 -0
  103. package/lib/factory/model/drop.js +15 -0
  104. package/lib/factory/model/exists.js +21 -0
  105. package/lib/factory/model/find-all-record.js +71 -0
  106. package/lib/factory/model/find-attachment.js +29 -0
  107. package/lib/factory/model/find-one-record.js +19 -0
  108. package/lib/factory/model/find-record.js +103 -0
  109. package/lib/factory/model/get-attachment.js +15 -0
  110. package/lib/factory/model/get-record.js +79 -0
  111. package/lib/factory/model/list-attachment.js +37 -0
  112. package/lib/{add-fixtures.js → factory/model/load-fixtures.js} +69 -67
  113. package/lib/factory/model/remove-attachment.js +15 -0
  114. package/lib/factory/model/remove-record.js +59 -0
  115. package/lib/factory/model/sanitize-body.js +62 -0
  116. package/lib/factory/model/sanitize-id.js +7 -0
  117. package/lib/factory/model/sanitize-record.js +26 -0
  118. package/lib/factory/model/update-attachment.js +9 -0
  119. package/lib/factory/model/update-record.js +81 -0
  120. package/lib/factory/model/upsert-record.js +95 -0
  121. package/lib/factory/model/validate.js +232 -0
  122. package/lib/factory/model.js +150 -0
  123. package/lib/index.js +3 -0
  124. package/package.json +45 -36
  125. package/wiki/APPLETS.md +57 -0
  126. package/wiki/CHANGES.md +46 -0
  127. package/wiki/CONFIG.md +25 -0
  128. package/wiki/CONTRIBUTING.md +5 -0
  129. package/wiki/DEV-GUIDE.md +1 -0
  130. package/wiki/ECOSYSTEM.md +20 -0
  131. package/wiki/GETTING-STARTED.md +166 -0
  132. package/{docs/query-language.md → wiki/QUERY-LANGUAGE.md} +0 -0
  133. package/wiki/USER-GUIDE.md +1 -0
  134. package/extend/bajoCli/applet/model-clear.js +0 -11
  135. package/extend/bajoCli/applet/model-rebuild.js +0 -101
  136. package/extend/bajoCli/applet/record-create.js +0 -41
  137. package/extend/bajoCli/applet/record-find.js +0 -27
  138. package/extend/bajoCli/applet/record-get.js +0 -24
  139. package/extend/bajoCli/applet/record-remove.js +0 -24
  140. package/extend/bajoCli/applet/record-update.js +0 -47
  141. package/extend/bajoCli/applet/schema.js +0 -22
  142. package/extend/bajoCli/applet/stat-count.js +0 -24
  143. package/lib/build-bulk-action.js +0 -12
  144. package/lib/check-unique.js +0 -39
  145. package/lib/collect-feature.js +0 -25
  146. package/lib/collect-schemas.js +0 -83
  147. package/lib/exec-feature-hook.js +0 -13
  148. package/lib/exec-validation.js +0 -21
  149. package/lib/generic-prop-sanitizer.js +0 -31
  150. package/lib/handle-attachment-upload.js +0 -16
  151. package/lib/mem-db/conn-sanitizer.js +0 -8
  152. package/lib/mem-db/instantiate.js +0 -41
  153. package/lib/mem-db/method/model/clear.js +0 -6
  154. package/lib/mem-db/method/model/create.js +0 -5
  155. package/lib/mem-db/method/model/drop.js +0 -5
  156. package/lib/mem-db/method/model/exists.js +0 -5
  157. package/lib/mem-db/method/record/create.js +0 -12
  158. package/lib/mem-db/method/record/find.js +0 -20
  159. package/lib/mem-db/method/record/get.js +0 -9
  160. package/lib/mem-db/method/record/remove.js +0 -13
  161. package/lib/mem-db/method/record/update.js +0 -15
  162. package/lib/mem-db/method/stat/count.js +0 -11
  163. package/lib/mem-db/start.js +0 -25
  164. package/lib/merge-attachment-info.js +0 -16
  165. package/lib/multi-rel-rows.js +0 -42
  166. package/lib/resolve-method.js +0 -16
  167. package/lib/sanitize-schema.js +0 -197
  168. package/lib/single-rel-rows.js +0 -38
  169. package/method/attachment/copy-uploaded.js +0 -34
  170. package/method/attachment/create.js +0 -29
  171. package/method/attachment/find.js +0 -27
  172. package/method/attachment/get-path.js +0 -12
  173. package/method/attachment/get.js +0 -12
  174. package/method/attachment/pre-check.js +0 -9
  175. package/method/attachment/remove.js +0 -11
  176. package/method/attachment/update.js +0 -7
  177. package/method/bulk/create.js +0 -46
  178. package/method/model/clear.js +0 -22
  179. package/method/model/create.js +0 -19
  180. package/method/model/drop.js +0 -19
  181. package/method/model/exists.js +0 -24
  182. package/method/record/clear.js +0 -24
  183. package/method/record/count.js +0 -44
  184. package/method/record/create.js +0 -71
  185. package/method/record/find-all.js +0 -25
  186. package/method/record/find-one.js +0 -56
  187. package/method/record/find.js +0 -52
  188. package/method/record/get.js +0 -47
  189. package/method/record/remove.js +0 -41
  190. package/method/record/update.js +0 -63
  191. package/method/record/upsert.js +0 -35
  192. package/method/sanitize/body.js +0 -70
  193. package/method/sanitize/date.js +0 -14
  194. package/method/sanitize/id.js +0 -7
  195. package/method/stat/aggregate.js +0 -23
  196. package/method/stat/histogram.js +0 -26
  197. package/method/validate.js +0 -157
@@ -1,25 +0,0 @@
1
- async function findAll (name, filter = {}, options = {}) {
2
- const { maxLimit, hardLimit } = this.config.default.filter
3
- filter.page = 1
4
- filter.limit = maxLimit
5
- options.dataOnly = true
6
- const match = filter.match
7
- const all = []
8
- let count = 0
9
- for (;;) {
10
- filter.match = match
11
- const results = await this.recordFind(name, filter, options)
12
- if (results.length === 0) break
13
- if (count + results.length > hardLimit) {
14
- const sliced = results.slice(0, hardLimit - count)
15
- all.push(...sliced)
16
- break
17
- }
18
- all.push(...results)
19
- count = count + results.length
20
- filter.page++
21
- }
22
- return all
23
- }
24
-
25
- export default findAll
@@ -1,56 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import singleRelRows from '../../lib/single-rel-rows.js'
3
- import execFeatureHook from '../../lib/exec-feature-hook.js'
4
-
5
- async function findOne (name, filter = {}, opts = {}) {
6
- const { isSet } = this.lib.aneka
7
- const { runHook } = this.app.bajo
8
- const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit, pick } = this.lib._
10
- delete opts.record
11
- const options = cloneDeep(omit(opts, ['req', 'reply']))
12
- options.req = opts.req
13
- options.reply = opts.reply
14
- options.dataOnly = options.dataOnly ?? true
15
- let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
16
- options.count = false
17
- options.dataOnly = false
18
- await this.modelExists(name, true)
19
- filter.limit = 1
20
- filter.page = 1
21
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-find', options)
22
- if (!schema.cacheable) noCache = true
23
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
24
- if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
25
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
26
- if (!noHook) {
27
- await runHook(`${this.name}:beforeRecordFindOne`, name, filter, options)
28
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordFindOne`, filter, options)
29
- }
30
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFindOne', { schema, filter, options })
31
- if (get && !noCache && !options.record) {
32
- const cachedResult = await get({ model: name, filter, options })
33
- if (cachedResult) {
34
- cachedResult.cached = true
35
- return dataOnly ? cachedResult.data : cachedResult
36
- }
37
- }
38
- filter.limit = 1
39
- filter.page = 1
40
- let record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
41
- delete options.record
42
- record.data = record.data[0]
43
-
44
- if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
45
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
46
- record = pick(record, ['data'])
47
- if (!noHook) {
48
- await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
49
- await runHook(`${this.name}:afterRecordFindOne`, name, filter, options, record)
50
- }
51
- if (set && !noCache) await set({ model: name, filter, options, record })
52
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterFindOne', { schema, filter, options, record })
53
- return dataOnly ? record.data : record
54
- }
55
-
56
- export default findOne
@@ -1,52 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import multiRelRows from '../../lib/multi-rel-rows.js'
3
- import execFeatureHook from '../../lib/exec-feature-hook.js'
4
-
5
- async function find (name, filter = {}, opts = {}) {
6
- const { isSet } = this.lib.aneka
7
- const { runHook } = this.app.bajo
8
- const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit } = this.lib._
10
- delete opts.records
11
- const options = cloneDeep(omit(opts, ['req', 'reply']))
12
- options.req = opts.req
13
- options.reply = opts.reply
14
- options.dataOnly = options.dataOnly ?? true
15
- let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
16
- options.count = options.count ?? false
17
- options.dataOnly = false
18
- await this.modelExists(name, true)
19
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-find', options)
20
- if (!schema.cacheable) noCache = true
21
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
22
- if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
23
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
24
- if (!noHook) {
25
- await runHook(`${this.name}:beforeRecordFind`, name, filter, options)
26
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordFind`, filter, options)
27
- }
28
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFind', { schema, filter, options })
29
- if (get && !noCache && !options.records) {
30
- const cachedResult = await get({ model: name, filter, options })
31
- if (cachedResult) {
32
- cachedResult.cached = true
33
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records: cachedResult })
34
- return dataOnly ? cachedResult.data : cachedResult
35
- }
36
- }
37
- const records = options.records ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
38
- delete options.records
39
- if (isSet(options.rels)) await multiRelRows.call(this, { schema, records: records.data, options })
40
- for (const idx in records.data) {
41
- records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
42
- }
43
- if (!noHook) {
44
- await runHook(`${this.name}.${camelCase(name)}:afterRecordFind`, filter, options, records)
45
- await runHook(`${this.name}:afterRecordFind`, name, filter, options, records)
46
- }
47
- if (set && !noCache) await set({ model: name, filter, options, records })
48
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
49
- return dataOnly ? records.data : records
50
- }
51
-
52
- export default find
@@ -1,47 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import singleRelRows from '../../lib/single-rel-rows.js'
3
- import execFeatureHook from '../../lib/exec-feature-hook.js'
4
-
5
- async function get (name, id, opts = {}) {
6
- const { isSet } = this.lib.aneka
7
- const { runHook } = this.app.bajo
8
- const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit } = this.lib._
10
- delete opts.record
11
- const options = cloneDeep(omit(opts, ['req', 'reply']))
12
- options.req = opts.req
13
- options.reply = opts.reply
14
- options.dataOnly = options.dataOnly ?? true
15
- let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden = [], forceNoHidden } = options
16
- await this.modelExists(name, true)
17
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-get', options)
18
- if (!schema.cacheable) noCache = true
19
- id = this.sanitizeId(id, schema)
20
- options.dataOnly = false
21
- if (!noHook) {
22
- await runHook(`${this.name}:beforeRecordGet`, name, id, options)
23
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordGet`, id, options)
24
- }
25
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeGet', { schema, id, options })
26
- if (get && !noCache && !options.record) {
27
- const cachedResult = await get({ model: name, id, options })
28
- if (cachedResult) {
29
- cachedResult.cached = true
30
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record: cachedResult })
31
- return dataOnly ? cachedResult.data : cachedResult
32
- }
33
- }
34
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
35
- delete options.record
36
- if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
37
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
38
- if (!noHook) {
39
- await runHook(`${this.name}.${camelCase(name)}:afterRecordGet`, id, options, record)
40
- await runHook(`${this.name}:afterRecordGet`, name, id, options, record)
41
- }
42
- if (set && !noCache) await set({ model: name, id, options, record })
43
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record })
44
- return dataOnly ? record.data : record
45
- }
46
-
47
- export default get
@@ -1,41 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import handleAttachmentUpload from '../../lib/handle-attachment-upload.js'
3
- import execFeatureHook from '../../lib/exec-feature-hook.js'
4
-
5
- async function remove (name, id, opts = {}) {
6
- const { runHook } = this.app.bajo
7
- const { clearModel } = this.cache ?? {}
8
- const { cloneDeep, camelCase, omit } = this.lib._
9
- delete opts.record
10
- const options = cloneDeep(omit(opts, ['req', 'reply']))
11
- options.req = opts.req
12
- options.reply = opts.reply
13
- options.dataOnly = options.dataOnly ?? true
14
- const { fields, dataOnly, noHook, noResult, noFeatureHook, hidden, forceNoHidden } = options
15
- options.dataOnly = false
16
- await this.modelExists(name, true)
17
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-remove', options)
18
- id = this.sanitizeId(id, schema)
19
- if (!noHook) {
20
- await runHook(`${this.name}:beforeRecordRemove`, name, id, options)
21
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordRemove`, id, options)
22
- }
23
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeRemove', { schema, id, options })
24
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
25
- delete options.record
26
- if (options.req) {
27
- if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, options, action: 'remove' })
28
- if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordRemoved'))
29
- }
30
- if (clearModel) await clearModel({ model: name, id, options, record })
31
- if (noResult) return
32
- record.oldData = options.record ? options.record.oldData : (await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden }))
33
- if (!noHook) {
34
- await runHook(`${this.name}.${camelCase(name)}:afterRecordRemove`, id, options, record)
35
- await runHook(`${this.name}:afterRecordRemove`, name, id, options, record)
36
- }
37
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterRemove', { schema, id, options, record })
38
- return dataOnly ? record.oldData : record
39
- }
40
-
41
- export default remove
@@ -1,63 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import checkUnique from '../../lib/check-unique.js'
3
- import handleAttachmentUpload from '../../lib/handle-attachment-upload.js'
4
- import execValidation from '../../lib/exec-validation.js'
5
- import execFeatureHook from '../../lib/exec-feature-hook.js'
6
- import singleRelRows from '../../lib/single-rel-rows.js'
7
-
8
- async function update (name, id, input, opts = {}) {
9
- const { isSet } = this.lib.aneka
10
- const { runHook } = this.app.bajo
11
- const { clearModel } = this.cache ?? {}
12
- const { forOwn, find, cloneDeep, camelCase, omit, get } = this.lib._
13
- delete opts.record
14
- const options = cloneDeep(omit(opts, ['req', 'reply']))
15
- options.req = opts.req
16
- options.reply = opts.reply
17
- options.dataOnly = options.dataOnly ?? true
18
- input = cloneDeep(input)
19
- const { fields, dataOnly, noHook, noValidation, noCheckUnique, noFeatureHook, noResult, noSanitize, partial = true, hidden, forceNoHidden } = options
20
- options.dataOnly = true
21
- options.truncateString = options.truncateString ?? true
22
- await this.modelExists(name, true)
23
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-update', options)
24
- id = this.sanitizeId(id, schema)
25
- const extFields = get(options, 'validation.extFields', [])
26
- let body = noSanitize ? input : await this.sanitizeBody({ body: input, schema, partial, strict: true, extFields })
27
- delete body.id
28
- if (!noHook) {
29
- await runHook(`${this.name}:beforeRecordUpdate`, name, id, body, options)
30
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordUpdate`, id, body, options)
31
- }
32
- if (!noValidation) body = await execValidation.call(this, { name, body, options, partial })
33
- if (!noCheckUnique) await checkUnique.call(this, { schema, body, id })
34
- const nbody = {}
35
- forOwn(body, (v, k) => {
36
- if (v === undefined) return undefined
37
- const prop = find(schema.properties, { name: k })
38
- if (!prop) return undefined
39
- if (options.truncateString && isSet(v) && ['string', 'text'].includes(prop.type)) v = v.slice(0, prop.maxLength)
40
- nbody[k] = v
41
- })
42
- delete nbody.id
43
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeUpdate', { schema, body: nbody, options })
44
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, body: nbody, options }))
45
- delete options.record
46
- if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
47
- if (options.req) {
48
- if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, body, options, action: 'update' })
49
- if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordUpdated'))
50
- }
51
- if (clearModel) await clearModel({ model: name, id, body: nbody, options, record })
52
- if (noResult) return
53
- record.oldData = await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden })
54
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
55
- if (!noHook) {
56
- await runHook(`${this.name}.${camelCase(name)}:afterRecordUpdate`, id, nbody, options, record)
57
- await runHook(`${this.name}:afterRecordUpdate`, name, id, nbody, options, record)
58
- }
59
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterUpdate', { schema, body: nbody, record })
60
- return dataOnly ? record.data : record
61
- }
62
-
63
- export default update
@@ -1,35 +0,0 @@
1
- async function upsert (name, input, opts = {}) {
2
- const { generateId } = this.app.bajo
3
- const { find } = this.lib._
4
- const { cloneDeep, omit, merge } = this.lib._
5
- const { query, omitOnUpdate = [], omitOnCreate = [] } = opts
6
- const options = cloneDeep(omit(opts, ['req', 'reply', 'query', 'omitOnUpdate', 'omitOnCreate']))
7
- options.req = opts.req
8
- options.reply = opts.reply
9
- options.dataOnly = options.dataOnly ?? true
10
- await this.modelExists(name, true)
11
- const { schema } = this.getInfo(name)
12
- const idField = find(schema.properties, { name: 'id' })
13
- let id
14
- if (idField.type === 'string') id = input.id ?? generateId()
15
- else if (idField.type === 'integer') id = input.id ?? generateId('int')
16
- id = this.sanitizeId(id, schema)
17
- let old
18
- let body
19
- const o = { dataOnly: true, noHook: true, noCache: true, hidden: options.hidden, forceNoHidden: options.forceNoHidden }
20
- if (query) {
21
- old = await this.recordFindOne(name, { query }, o)
22
- } else {
23
- o.thrownNotFound = false
24
- old = await this.recordGet(name, id, o)
25
- }
26
- if (old) {
27
- body = merge(omit(old, ['id', 'createdAt', 'updatedAt', 'removedAt']), omit(input, omitOnUpdate))
28
- return await this.recordUpdate(name, old.id, body, options)
29
- }
30
- if (!query) input.id = id
31
- body = omit(input, omitOnCreate)
32
- return await this.recordCreate(name, body, options)
33
- }
34
-
35
- export default upsert
@@ -1,70 +0,0 @@
1
- async function sanitizeBody ({ body = {}, schema = {}, partial, strict, extFields = [] }) {
2
- const { isSet } = this.lib.aneka
3
- const { dayjs } = this.lib
4
- const { callHandler } = this.app.bajo
5
- const { has, isString, isNumber, concat, isNaN } = this.lib._
6
- const result = {}
7
- for (const p of concat(schema.properties, extFields)) {
8
- if (partial && !has(body, p.name)) continue
9
- if (['object', 'array'].includes(p.type)) {
10
- if (isString(body[p.name])) {
11
- try {
12
- result[p.name] = JSON.parse(body[p.name])
13
- } catch (err) {
14
- result[p.name] = null
15
- }
16
- } else {
17
- try {
18
- result[p.name] = JSON.parse(JSON.stringify(body[p.name]))
19
- } catch (err) {}
20
- }
21
- } else result[p.name] = body[p.name]
22
- if (isSet(body[p.name])) {
23
- if (p.type === 'boolean') result[p.name] = result[p.name] === null ? null : (['true', true].includes(result[p.name]))
24
- if (['float', 'double'].includes(p.type)) {
25
- if (isNumber(body[p.name])) result[p.name] = body[p.name]
26
- else if (strict) {
27
- result[p.name] = Number(body[p.name])
28
- } else {
29
- result[p.name] = parseFloat(body[p.name]) || null
30
- }
31
- }
32
- if (['integer', 'smallint'].includes(p.type)) {
33
- if (isNumber(body[p.name])) result[p.name] = body[p.name]
34
- else if (strict) {
35
- result[p.name] = Number(body[p.name])
36
- } else {
37
- result[p.name] = parseInt(body[p.name]) || null
38
- }
39
- }
40
- if (p.type === 'timestamp') {
41
- if (!isNumber(body[p.name])) result[p.name] = -1
42
- else {
43
- const dt = dayjs.unix(body[p.name])
44
- result[p.name] = dt.isValid() ? dt.unix() : -1
45
- }
46
- } else {
47
- for (const t of ['datetime', 'date|YYYY-MM-DD', 'time|HH:mm:ss']) {
48
- const [type, input] = t.split('|')
49
- if (p.type === type) result[p.name] = this.sanitizeDate(body[p.name], { input })
50
- }
51
- }
52
- if (['string', 'text'].includes(p.type)) result[p.name] = body[p.name] + ''
53
- } else {
54
- if (isSet(p.default)) {
55
- result[p.name] = p.default
56
- if (isString(p.default) && p.default.startsWith('handler:')) {
57
- const [, ...args] = p.default.split(':')
58
- if (args.length > 0) result[p.name] = await callHandler(args.join(':'))
59
- } else {
60
- if (['float', 'double'].includes(p.type)) result[p.name] = parseFloat(result[p.name])
61
- if (['integer', 'smallint'].includes(p.type)) result[p.name] = parseInt(result[p.name])
62
- if (isNaN(result[p.name])) result[p.name] = null
63
- }
64
- }
65
- }
66
- }
67
- return result
68
- }
69
-
70
- export default sanitizeBody
@@ -1,14 +0,0 @@
1
- function sanitizeDate (value, { input, output, silent = true } = {}) {
2
- const { dayjs } = this.lib
3
- if (value === 0) return null
4
- if (!output) output = input
5
- const dt = dayjs(value, input)
6
- if (!dt.isValid()) {
7
- if (silent) return -1
8
- throw this.error('invalidDate')
9
- }
10
- if (output === 'native' || !output) return dt.toDate()
11
- return dt.format(output)
12
- }
13
-
14
- export default sanitizeDate
@@ -1,7 +0,0 @@
1
- function sanitizeId (id, schema) {
2
- const prop = schema.properties.find(p => p.name === 'id')
3
- if (prop.type === 'integer') id = parseInt(id)
4
- return id
5
- }
6
-
7
- export default sanitizeId
@@ -1,23 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- async function aggregate (name, filter = {}, options = {}) {
4
- const { runHook } = this.app.bajo
5
- const { dataOnly = true, noHook, aggregate } = options
6
- options.dataOnly = false
7
- await this.modelExists(name, true)
8
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'stat-aggregate', options)
9
- if (!noHook) {
10
- await runHook(`${this.name}:beforeStatAggregate`, name, aggregate, filter, options)
11
- await runHook(`${this.name}.${name}:beforeStatAggregate`, aggregate, filter, options)
12
- }
13
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
14
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
15
- const rec = await handler.call(this.app[driver.ns], { schema, filter, options })
16
- if (!noHook) {
17
- await runHook(`${this.name}.${name}:afterStatAggregate`, aggregate, filter, options, rec)
18
- await runHook(`${this.name}:afterStatAggregate`, name, aggregate, filter, options, rec)
19
- }
20
- return dataOnly ? rec.data : rec
21
- }
22
-
23
- export default aggregate
@@ -1,26 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- const types = ['daily', 'monthly', 'yearly']
4
-
5
- async function histogram (name, filter = {}, options = {}) {
6
- const { runHook, join } = this.app.bajo
7
- const { dataOnly = true, noHook, type } = options
8
- options.dataOnly = false
9
- if (!types.includes(type)) throw this.error('histogramTypeMusBe%s', join(types))
10
- await this.modelExists(name, true)
11
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'stat-histogram', options)
12
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
13
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
14
- if (!noHook) {
15
- await runHook(`${this.name}:beforeStatHistogram`, name, type, filter, options)
16
- await runHook(`${this.name}.${name}:beforeStatHistogram`, type, filter, options)
17
- }
18
- const rec = await handler.call(this.app[driver.ns], { schema, type, filter, options })
19
- if (!noHook) {
20
- await runHook(`${this.name}.${name}:afterStatHistogram`, type, filter, options, rec)
21
- await runHook(`${this.name}:afterStatHistogram`, name, type, filter, options, rec)
22
- }
23
- return dataOnly ? rec.data : rec
24
- }
25
-
26
- export default histogram
@@ -1,157 +0,0 @@
1
- import joi from 'joi'
2
-
3
- const excludedTypes = ['object', 'array']
4
- const excludedNames = []
5
-
6
- const validator = {
7
- string: ['alphanum', 'base64', 'case', 'creditCard', 'dataUri', 'domain', 'email', 'guid',
8
- 'uuid', 'hex', 'hostname', 'insensitive', 'ip', 'isoDate', 'isoDuration', 'length', 'lowercase',
9
- 'max', 'min', 'normalize', 'pattern', 'regex', 'replace', 'token', 'trim', 'truncate',
10
- 'uppercase', 'uri'],
11
- number: ['great', 'less', 'max', 'min', 'multiple', 'negative', 'port', 'positive',
12
- 'sign', 'unsafe'],
13
- boolean: ['falsy', 'sensitive', 'truthy'],
14
- date: ['greater', 'iso', 'less', 'max', 'min'],
15
- timestamp: ['timestamp']
16
- }
17
-
18
- function buildFromDbSchema (schema, { fields = [], rule = {}, extFields = [] } = {}) {
19
- // if (schema.validation) return schema.validation
20
- const {
21
- isPlainObject, get, each, isEmpty, isString, forOwn, keys,
22
- find, isArray, has, cloneDeep, concat, without
23
- } = this.lib._
24
- const obj = {}
25
- const me = this
26
- const refs = []
27
-
28
- function getRuleKv (kvRule) {
29
- let key
30
- let value
31
- let columns
32
- if (isPlainObject(kvRule)) {
33
- key = kvRule.rule
34
- value = kvRule.params
35
- columns = kvRule.fields
36
- } else if (isString(kvRule)) {
37
- [key, value, columns] = kvRule.split(':')
38
- }
39
- return { key, value, columns }
40
- }
41
-
42
- function applyFieldRules (prop, obj) {
43
- const minMax = { min: false, max: false }
44
- const rules = get(rule, prop.name, prop.rules ?? [])
45
- if (!isArray(rules)) return rules
46
- each(rules, r => {
47
- const types = validator[me.propType[prop.type].validator]
48
- const { key, value } = getRuleKv(r)
49
- if (keys(minMax).includes(key)) minMax[key] = true
50
- if (key === 'ref') {
51
- refs.push(prop.name)
52
- obj = joi.ref(value)
53
- return undefined
54
- }
55
- if (!key || !types.includes(key)) return undefined
56
- obj = obj[key](value)
57
- })
58
- if (refs.includes(prop.name)) return obj
59
- if (['string', 'text'].includes(prop.type)) {
60
- forOwn(minMax, (v, k) => {
61
- if (v) return undefined
62
- if (has(prop, `${k}Length`)) obj = obj[k](prop[`${k}Length`])
63
- })
64
- }
65
- if (isArray(prop.values)) obj = obj.valid(...prop.values)
66
- if (!['id'].includes(prop.name) && prop.required) obj = obj.required()
67
- return obj
68
- }
69
-
70
- const props = concat(cloneDeep(schema.properties), extFields)
71
-
72
- for (const p of props) {
73
- if (excludedTypes.includes(p.type) || excludedNames.includes(p.name)) continue
74
- if (fields.length > 0 && !fields.includes(p.name)) continue
75
- let item
76
- switch (p.type) {
77
- case 'text':
78
- case 'string': {
79
- item = applyFieldRules(p, joi.string())
80
- break
81
- }
82
- case 'smallint':
83
- case 'integer':
84
- item = applyFieldRules(p, joi.number().integer())
85
- break
86
- case 'float':
87
- case 'double':
88
- if (p.precision) item = applyFieldRules(p, joi.number().precision(p.precision))
89
- else item = applyFieldRules(p, joi.number())
90
- break
91
- case 'time':
92
- case 'date':
93
- case 'datetime':
94
- item = applyFieldRules(p, joi.date())
95
- break
96
- case 'timestamp':
97
- item = applyFieldRules(p, joi.number().integer())
98
- break
99
- case 'boolean':
100
- item = applyFieldRules(p, joi.boolean())
101
- break
102
- }
103
- if (item) {
104
- if (item.$_root) obj[p.name] = item.allow(null)
105
- else obj[p.name] = item
106
- }
107
- }
108
- if (isEmpty(obj)) return false
109
- each(get(schema, 'globalRules', []), r => {
110
- each(without(keys(obj), ...refs), k => {
111
- const prop = find(props, { name: k })
112
- if (!prop) return undefined
113
- const types = validator[me.propType[prop.type].validator]
114
- const { key, value, columns = [] } = getRuleKv(r)
115
- if (!types.includes(key)) return undefined
116
- if (columns.length === 0 || columns.includes(k)) obj[k] = obj[k][key](value)
117
- })
118
- })
119
- const result = joi.object(obj)
120
- if (fields.length === 0) return result
121
- each(['with', 'xor', 'without'], k => {
122
- const item = get(schema, `extRule.${k}`)
123
- if (item) result[k](...item)
124
- })
125
- return result
126
- }
127
-
128
- async function validate (value, joiSchema, { ns, fields, extFields, params } = {}) {
129
- const { defaultsDeep, isSet } = this.lib.aneka
130
- const { isString, forOwn, find } = this.lib._
131
-
132
- ns = ns ?? [this.name]
133
- params = defaultsDeep(params, this.config.validationParams)
134
- const { rule = {} } = params
135
- delete params.rule
136
- if (isString(joiSchema)) {
137
- const { schema } = this.getInfo(joiSchema)
138
- forOwn(value, (v, k) => {
139
- if (!isSet(v)) return undefined
140
- const p = find(schema.properties, { name: k })
141
- if (!p) return undefined
142
- for (const t of ['date|YYYY-MM-DD', 'time|HH:mm:ss']) {
143
- const [type, input] = t.split('|')
144
- if (p.type === type) value[k] = this.sanitizeDate(value[k], { input, output: 'native' })
145
- }
146
- })
147
- joiSchema = buildFromDbSchema.call(this, schema, { fields, rule, extFields })
148
- }
149
- if (!joiSchema) return value
150
- try {
151
- return await joiSchema.validateAsync(value, params)
152
- } catch (err) {
153
- throw this.error('validationError', { details: err.details, values: err.values, ns, statusCode: 422, code: 'DB_VALIDATION' })
154
- }
155
- }
156
-
157
- export default validate