dobo 2.0.0 → 2.0.1

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 (139) 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 +726 -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 +369 -0
  44. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  45. package/docs/scripts/third-party/hljs-original.js +5171 -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 +672 -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 +3 -2
  62. package/extend/bajo/intl/id.json +3 -2
  63. package/extend/bajoCli/applet/connection.js +5 -5
  64. package/extend/bajoCli/applet/lib/post-process.js +22 -16
  65. package/extend/bajoCli/applet/model-clear.js +4 -4
  66. package/extend/bajoCli/applet/model-rebuild.js +13 -13
  67. package/extend/bajoCli/applet/record-create.js +10 -8
  68. package/extend/bajoCli/applet/record-find.js +6 -5
  69. package/extend/bajoCli/applet/record-get.js +5 -5
  70. package/extend/bajoCli/applet/record-remove.js +5 -5
  71. package/extend/bajoCli/applet/record-update.js +9 -9
  72. package/extend/bajoCli/applet/schema.js +4 -4
  73. package/extend/bajoCli/applet/stat-count.js +4 -4
  74. package/extend/dobo/feature/created-at.js +1 -1
  75. package/extend/dobo/feature/removed-at.js +3 -3
  76. package/extend/dobo/feature/updated-at.js +2 -2
  77. package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +3 -3
  78. package/index.js +230 -72
  79. package/lib/add-fixtures.js +5 -5
  80. package/lib/build-bulk-action.js +2 -2
  81. package/lib/check-unique.js +2 -2
  82. package/lib/collect-connections.js +14 -3
  83. package/lib/collect-drivers.js +14 -6
  84. package/lib/{collect-feature.js → collect-features.js} +12 -4
  85. package/lib/collect-schemas.js +17 -9
  86. package/lib/exec-feature-hook.js +1 -1
  87. package/lib/exec-validation.js +5 -5
  88. package/lib/generic-prop-sanitizer.js +6 -5
  89. package/lib/handle-attachment-upload.js +2 -2
  90. package/lib/index.js +3 -0
  91. package/lib/mem-db/conn-sanitizer.js +1 -1
  92. package/lib/mem-db/instantiate.js +4 -4
  93. package/lib/mem-db/method/record/find.js +1 -1
  94. package/lib/mem-db/method/record/get.js +1 -1
  95. package/lib/mem-db/method/record/remove.js +1 -1
  96. package/lib/mem-db/method/record/update.js +1 -1
  97. package/lib/mem-db/start.js +1 -1
  98. package/lib/merge-attachment-info.js +2 -2
  99. package/lib/multi-rel-rows.js +2 -2
  100. package/lib/resolve-method.js +3 -3
  101. package/lib/sanitize-schema.js +8 -7
  102. package/lib/single-rel-rows.js +2 -2
  103. package/method/attachment/copy-uploaded.js +2 -2
  104. package/method/attachment/create.js +3 -3
  105. package/method/attachment/find.js +2 -2
  106. package/method/attachment/get-path.js +3 -3
  107. package/method/attachment/get.js +1 -1
  108. package/method/attachment/pre-check.js +1 -1
  109. package/method/attachment/remove.js +1 -1
  110. package/method/bulk/create.js +6 -6
  111. package/method/model/clear.js +5 -5
  112. package/method/model/create.js +18 -5
  113. package/method/model/drop.js +17 -5
  114. package/method/model/exists.js +18 -5
  115. package/method/record/clear.js +5 -5
  116. package/method/record/count.js +27 -5
  117. package/method/record/create.js +46 -6
  118. package/method/record/find-all.js +16 -0
  119. package/method/record/find-one.js +20 -6
  120. package/method/record/find.js +69 -6
  121. package/method/record/get.js +48 -6
  122. package/method/record/remove.js +36 -5
  123. package/method/record/update.js +47 -6
  124. package/method/record/upsert.js +18 -2
  125. package/method/sanitize/body.js +18 -3
  126. package/method/sanitize/date.js +20 -7
  127. package/method/sanitize/id.js +10 -0
  128. package/method/stat/aggregate.js +4 -4
  129. package/method/stat/histogram.js +4 -4
  130. package/method/validate.js +96 -7
  131. package/package.json +41 -36
  132. package/wiki/APPLETS.md +57 -0
  133. package/wiki/CONFIG.md +25 -0
  134. package/wiki/CONTRIBUTING.md +5 -0
  135. package/wiki/DEV-GUIDE.md +1 -0
  136. package/wiki/ECOSYSTEM.md +20 -0
  137. package/wiki/GETTING-STARTED.md +166 -0
  138. package/wiki/USER-GUIDE.md +1 -0
  139. /package/{docs/query-language.md → wiki/QUERY-LANGUAGE.md} +0 -0
@@ -6,11 +6,51 @@ import execValidation from '../../lib/exec-validation.js'
6
6
  import execFeatureHook from '../../lib/exec-feature-hook.js'
7
7
  import singleRelRows from '../../lib/single-rel-rows.js'
8
8
 
9
+ /**
10
+ * @typedef {Object} TRecordCreateOptions
11
+ * @see Dobo#recordCreate
12
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns record's object. Otherwise {@link TRecordCreateResult}
13
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
14
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
15
+ * @property {boolean} [noValidation=false] - If ```true```, no validation of data payload performed
16
+ * @property {boolean} [noCheckUnique=false] - If ```true```, no unique validation for ID performed
17
+ * @property {boolean} [noSanitize=false] - If ```true```, accept data payload as is without sanitization
18
+ * @property {boolean} [noResult=false] - If ```true```, returns nothing
19
+ * @property {boolean} [truncateString=true] - If ```true``` (default), string is truncated to its schema's ```maxLemngth```
20
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
21
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's schema
22
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
23
+ */
24
+
25
+ /**
26
+ * Create a new record
27
+ *
28
+ * Example:
29
+ * ```javascript
30
+ * const { recordCreate } = this.app.dobo
31
+ * const { body } = {
32
+ * id: 'ID',
33
+ * name: 'Indonesia',
34
+ * iso3: 'IDN'
35
+ * }
36
+ * const result = await recordCreate('CdbCountry', body)
37
+ * ```
38
+ *
39
+ * @method
40
+ * @memberof Dobo
41
+ * @async
42
+ * @instance
43
+ * @name recordCreate
44
+ * @param {string} name - Model's name
45
+ * @param {Object} body - Data to be saved
46
+ * @param {TRecordCreateOptions} [options={}]
47
+ * @returns {(TRecordCreateResult|Object)} Returns newly created record if ```options.dataOnly``` is set. {@link TRecordCreateResult} otherwise
48
+ */
9
49
  async function create (name, input, opts = {}) {
10
50
  const { generateId, runHook } = this.app.bajo
11
- const { isSet } = this.lib.aneka
51
+ const { isSet } = this.app.lib.aneka
12
52
  const { clearModel } = this.cache ?? {}
13
- const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.lib._
53
+ const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.app.lib._
14
54
  delete opts.record
15
55
  const options = cloneDeep(omit(opts, ['req', 'reply']))
16
56
  options.req = opts.req
@@ -26,8 +66,8 @@ async function create (name, input, opts = {}) {
26
66
  const extFields = get(options, 'validation.extFields', [])
27
67
  let body = noSanitize ? cloneDeep(input) : await this.sanitizeBody({ body: input, schema, extFields, strict: true })
28
68
  if (!noHook) {
29
- await runHook(`${this.name}:beforeRecordCreate`, name, body, options)
30
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordCreate`, body, options)
69
+ await runHook(`${this.ns}:beforeRecordCreate`, name, body, options)
70
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordCreate`, body, options)
31
71
  }
32
72
  if (!isSet(body.id)) {
33
73
  if (idField.type === 'string') {
@@ -61,8 +101,8 @@ async function create (name, input, opts = {}) {
61
101
  if (noResult) return
62
102
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
63
103
  if (!noHook) {
64
- await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
65
- await runHook(`${this.name}:afterRecordCreate`, name, nbody, options, record)
104
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
105
+ await runHook(`${this.ns}:afterRecordCreate`, name, nbody, options, record)
66
106
  }
67
107
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body: nbody, options, record })
68
108
  return dataOnly ? record.data : record
@@ -1,3 +1,19 @@
1
+ /**
2
+ * Find all records by model's name and given filter.
3
+ *
4
+ * The total number of records returned is limited by ```hardLimit``` value set in {@tutorial config} file.
5
+ *
6
+ * @see Dobo#recordFind
7
+ * @method
8
+ * @memberof Dobo
9
+ * @async
10
+ * @instance
11
+ * @name recordFindAll
12
+ * @param {string} name - Model's name
13
+ * @param {Object} [filter={}] - Filter object
14
+ * @param {TRecordFindOptions} [options={}]
15
+ * @returns {(TRecordFindResult|Array.<Object>)} Return ```array``` of records if ```options.dataOnly``` is set. {@link TRecordFindResult} otherwise
16
+ */
1
17
  async function findAll (name, filter = {}, options = {}) {
2
18
  const { maxLimit, hardLimit } = this.config.default.filter
3
19
  filter.page = 1
@@ -2,11 +2,25 @@ import resolveMethod from '../../lib/resolve-method.js'
2
2
  import singleRelRows from '../../lib/single-rel-rows.js'
3
3
  import execFeatureHook from '../../lib/exec-feature-hook.js'
4
4
 
5
+ /**
6
+ * Find the first record by model's name and given filter.
7
+ *
8
+ * @see Dobo#recordFind
9
+ * @method
10
+ * @memberof Dobo
11
+ * @async
12
+ * @instance
13
+ * @name recordFindOne
14
+ * @param {string} name - Model's name
15
+ * @param {Object} [filter={}] - Filter object
16
+ * @param {TRecordFindOptions} [options={}]
17
+ * @returns {(TRecordGetResult|Object)} Return record's ```object``` if ```options.dataOnly``` is set. {@link TRecordGetResult} otherwise
18
+ */
5
19
  async function findOne (name, filter = {}, opts = {}) {
6
- const { isSet } = this.lib.aneka
20
+ const { isSet } = this.app.lib.aneka
7
21
  const { runHook } = this.app.bajo
8
22
  const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit, pick } = this.lib._
23
+ const { cloneDeep, camelCase, omit, pick } = this.app.lib._
10
24
  delete opts.record
11
25
  const options = cloneDeep(omit(opts, ['req', 'reply']))
12
26
  options.req = opts.req
@@ -24,8 +38,8 @@ async function findOne (name, filter = {}, opts = {}) {
24
38
  if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
25
39
  filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
26
40
  if (!noHook) {
27
- await runHook(`${this.name}:beforeRecordFindOne`, name, filter, options)
28
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordFindOne`, filter, options)
41
+ await runHook(`${this.ns}:beforeRecordFindOne`, name, filter, options)
42
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordFindOne`, filter, options)
29
43
  }
30
44
  if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFindOne', { schema, filter, options })
31
45
  if (get && !noCache && !options.record) {
@@ -45,8 +59,8 @@ async function findOne (name, filter = {}, opts = {}) {
45
59
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
46
60
  record = pick(record, ['data'])
47
61
  if (!noHook) {
48
- await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
49
- await runHook(`${this.name}:afterRecordFindOne`, name, filter, options, record)
62
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
63
+ await runHook(`${this.ns}:afterRecordFindOne`, name, filter, options, record)
50
64
  }
51
65
  if (set && !noCache) await set({ model: name, filter, options, record })
52
66
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterFindOne', { schema, filter, options, record })
@@ -2,11 +2,74 @@ import resolveMethod from '../../lib/resolve-method.js'
2
2
  import multiRelRows from '../../lib/multi-rel-rows.js'
3
3
  import execFeatureHook from '../../lib/exec-feature-hook.js'
4
4
 
5
+ /**
6
+ * @typedef {Object} TRecordFilter
7
+ * @see Dobo#recordFind
8
+ * @see Dobo#recordFindOne
9
+ * @see Dobo#recordFindAll
10
+ * @property {(string|Object)} [query={}] - Query definition. See {@tutorial query-language} for more
11
+ * @property {number} limit - Max number of records per page
12
+ * @property {number} page - Which page is the returned records currently at
13
+ * @property {number} skip - Records to skip
14
+ * @property {TRecordSort} sort - Sort order info
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} TRecordFindResult
19
+ * @see Dobo#recordFind
20
+ * @see Dobo#recordFindAll
21
+ * @see Dobo#recordGet
22
+ * @property {Array.<Object>} data - Array of returned records
23
+ * @property {boolean} success - Whether operation is successfull or failed
24
+ * @property {number} page - Which page is the returned records currently at
25
+ * @property {number} limit - Max number of records per page
26
+ * @property {number} count - Total number of records returned
27
+ * @property {number} pages - Total number of pages returned
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} TRecordFindOptions
32
+ * @see Dobo#recordFind
33
+ * @see Dobo#recordFindOne
34
+ * @see Dobo#recordFindAll
35
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
36
+ * @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
37
+ * @property {boolean} [noCache=true] - If ```true``` (default), result set won't be cached. This will overwrite model's ```cacheable``` property. Only applicable if {@link https://github.com/ardhi/bajo-cache|bajo-cache} is loaded
38
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
39
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
40
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
41
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's schema
42
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
43
+ */
44
+
45
+ /**
46
+ * Find records by model's name and given filter
47
+ *
48
+ * Example: find records from model **CdbCountry** where its id is 'ID' or 'MY',
49
+ * sorted by ```name``` in ascending order and return only its ```id```, ```name``` and ```iso3```
50
+ * ```javascript
51
+ * const { recordFind } = this.app.dobo
52
+ * const query = { id: { $in: ['ID', 'MY'] } }
53
+ * const sort = { name: 1 }
54
+ * const fields = ['id', 'name', 'iso3']
55
+ * const result = await recordFind('CdbCountry', { query, sort }, { fields })
56
+ * ```
57
+ *
58
+ * @method
59
+ * @memberof Dobo
60
+ * @async
61
+ * @instance
62
+ * @name recordFind
63
+ * @param {string} name - Model's name
64
+ * @param {Object} [filter={}] - Filter object
65
+ * @param {TRecordFindOptions} [options={}]
66
+ * @returns {(TRecordFindResult|Array.<Object>)} Return ```array``` of records if ```options.dataOnly``` is set. {@link TRecordFindResult} otherwise
67
+ */
5
68
  async function find (name, filter = {}, opts = {}) {
6
- const { isSet } = this.lib.aneka
69
+ const { isSet } = this.app.lib.aneka
7
70
  const { runHook } = this.app.bajo
8
71
  const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit } = this.lib._
72
+ const { cloneDeep, camelCase, omit } = this.app.lib._
10
73
  delete opts.records
11
74
  const options = cloneDeep(omit(opts, ['req', 'reply']))
12
75
  options.req = opts.req
@@ -22,8 +85,8 @@ async function find (name, filter = {}, opts = {}) {
22
85
  if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
23
86
  filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
24
87
  if (!noHook) {
25
- await runHook(`${this.name}:beforeRecordFind`, name, filter, options)
26
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordFind`, filter, options)
88
+ await runHook(`${this.ns}:beforeRecordFind`, name, filter, options)
89
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordFind`, filter, options)
27
90
  }
28
91
  if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFind', { schema, filter, options })
29
92
  if (get && !noCache && !options.records) {
@@ -41,8 +104,8 @@ async function find (name, filter = {}, opts = {}) {
41
104
  records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
42
105
  }
43
106
  if (!noHook) {
44
- await runHook(`${this.name}.${camelCase(name)}:afterRecordFind`, filter, options, records)
45
- await runHook(`${this.name}:afterRecordFind`, name, filter, options, records)
107
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordFind`, filter, options, records)
108
+ await runHook(`${this.ns}:afterRecordFind`, name, filter, options, records)
46
109
  }
47
110
  if (set && !noCache) await set({ model: name, filter, options, records })
48
111
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
@@ -2,11 +2,53 @@ import resolveMethod from '../../lib/resolve-method.js'
2
2
  import singleRelRows from '../../lib/single-rel-rows.js'
3
3
  import execFeatureHook from '../../lib/exec-feature-hook.js'
4
4
 
5
+ /**
6
+ * @typedef {Object} TRecordGetResult
7
+ * @see Dobo#recordGet
8
+ * @see Dobo#recordFindOne
9
+ * @property {Object} data - Returned record
10
+ * @property {boolean} success - Whether operation is successfull or failed
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} TRecordGetOptions
15
+ * @see Dobo#recordGet
16
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
17
+ * @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
18
+ * @property {boolean} [noCache=true] - If ```true``` (default), result set won't be cached. This will overwrite model's ```cacheable``` property. Only applicable if {@link https://github.com/ardhi/bajo-cache|bajo-cache} is loaded
19
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
20
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
21
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
22
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's schema
23
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
24
+ */
25
+
26
+ /**
27
+ * Get record by model's name and record ID
28
+ *
29
+ * Example:
30
+ * ```javascript
31
+ * const { recordGet } = this.app.dobo
32
+ * const fields = ['id', 'name', 'iso3']
33
+ * const result = await recordGet('CdbCountry', 'ID', { fields })
34
+ * ```
35
+ *
36
+ * @method
37
+ * @memberof Dobo
38
+ * @async
39
+ * @instance
40
+ * @name recordGet
41
+ * @param {string} name - Model's name
42
+ * @param {(string|number)} - Record's ID
43
+ * @param {TRecordGetOptions} [options={}]
44
+ * @returns {(TRecordGetResult|Object)} Return record's ```object``` if ```options.dataOnly``` is set. {@link TRecordGetResult} otherwise
45
+ */
46
+
5
47
  async function get (name, id, opts = {}) {
6
- const { isSet } = this.lib.aneka
48
+ const { isSet } = this.app.lib.aneka
7
49
  const { runHook } = this.app.bajo
8
50
  const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit } = this.lib._
51
+ const { cloneDeep, camelCase, omit } = this.app.lib._
10
52
  delete opts.record
11
53
  const options = cloneDeep(omit(opts, ['req', 'reply']))
12
54
  options.req = opts.req
@@ -19,8 +61,8 @@ async function get (name, id, opts = {}) {
19
61
  id = this.sanitizeId(id, schema)
20
62
  options.dataOnly = false
21
63
  if (!noHook) {
22
- await runHook(`${this.name}:beforeRecordGet`, name, id, options)
23
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordGet`, id, options)
64
+ await runHook(`${this.ns}:beforeRecordGet`, name, id, options)
65
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordGet`, id, options)
24
66
  }
25
67
  if (!noFeatureHook) await execFeatureHook.call(this, 'beforeGet', { schema, id, options })
26
68
  if (get && !noCache && !options.record) {
@@ -36,8 +78,8 @@ async function get (name, id, opts = {}) {
36
78
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
37
79
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
38
80
  if (!noHook) {
39
- await runHook(`${this.name}.${camelCase(name)}:afterRecordGet`, id, options, record)
40
- await runHook(`${this.name}:afterRecordGet`, name, id, options, record)
81
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordGet`, id, options, record)
82
+ await runHook(`${this.ns}:afterRecordGet`, name, id, options, record)
41
83
  }
42
84
  if (set && !noCache) await set({ model: name, id, options, record })
43
85
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record })
@@ -2,10 +2,41 @@ import resolveMethod from '../../lib/resolve-method.js'
2
2
  import handleAttachmentUpload from '../../lib/handle-attachment-upload.js'
3
3
  import execFeatureHook from '../../lib/exec-feature-hook.js'
4
4
 
5
+ /**
6
+ * @typedef {Object} TRecordRemoveOptions
7
+ * @see Dobo#recordRemove
8
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns deleted record. Otherwise {@link TRecordRemoveResult}
9
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
10
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
11
+ * @property {boolean} [noResult=false] - If ```true```, returns nothing
12
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
13
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's schema
14
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
15
+ */
16
+
17
+ /**
18
+ * Remove existing record by it's ID. All attachments bound to this record will also be removed forever.
19
+ *
20
+ * Example:
21
+ * ```javascript
22
+ * const { recordRemove } = this.app.dobo
23
+ * const result = await recordRemove('CdbCountry', 'ID')
24
+ * ```
25
+ *
26
+ * @method
27
+ * @memberof Dobo
28
+ * @async
29
+ * @instance
30
+ * @name recordRemove
31
+ * @param {string} name - Model's name
32
+ * @param {(string|number)} id - Record's ID
33
+ * @param {TRecordRemoveOptions} [options={}]
34
+ * @returns {(TRecordRemoveResult|Object)} Return the removed record if ```options.dataOnly``` is set. {@link TRecordRemoveResult} otherwise
35
+ */
5
36
  async function remove (name, id, opts = {}) {
6
37
  const { runHook } = this.app.bajo
7
38
  const { clearModel } = this.cache ?? {}
8
- const { cloneDeep, camelCase, omit } = this.lib._
39
+ const { cloneDeep, camelCase, omit } = this.app.lib._
9
40
  delete opts.record
10
41
  const options = cloneDeep(omit(opts, ['req', 'reply']))
11
42
  options.req = opts.req
@@ -17,8 +48,8 @@ async function remove (name, id, opts = {}) {
17
48
  const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-remove', options)
18
49
  id = this.sanitizeId(id, schema)
19
50
  if (!noHook) {
20
- await runHook(`${this.name}:beforeRecordRemove`, name, id, options)
21
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordRemove`, id, options)
51
+ await runHook(`${this.ns}:beforeRecordRemove`, name, id, options)
52
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordRemove`, id, options)
22
53
  }
23
54
  if (!noFeatureHook) await execFeatureHook.call(this, 'beforeRemove', { schema, id, options })
24
55
  const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
@@ -31,8 +62,8 @@ async function remove (name, id, opts = {}) {
31
62
  if (noResult) return
32
63
  record.oldData = options.record ? options.record.oldData : (await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden }))
33
64
  if (!noHook) {
34
- await runHook(`${this.name}.${camelCase(name)}:afterRecordRemove`, id, options, record)
35
- await runHook(`${this.name}:afterRecordRemove`, name, id, options, record)
65
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordRemove`, id, options, record)
66
+ await runHook(`${this.ns}:afterRecordRemove`, name, id, options, record)
36
67
  }
37
68
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterRemove', { schema, id, options, record })
38
69
  return dataOnly ? record.oldData : record
@@ -5,11 +5,52 @@ import execValidation from '../../lib/exec-validation.js'
5
5
  import execFeatureHook from '../../lib/exec-feature-hook.js'
6
6
  import singleRelRows from '../../lib/single-rel-rows.js'
7
7
 
8
+ /**
9
+ * @typedef {Object} TRecordUpdateOptions
10
+ * @see Dobo#recordUpdate
11
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns record's object. Otherwise {@link TRecordUpdateResult}
12
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
13
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
14
+ * @property {boolean} [noValidation=false] - If ```true```, no validation of data payload performed
15
+ * @property {boolean} [noCheckUnique=false] - If ```true```, no unique validation for ID performed
16
+ * @property {boolean} [noSanitize=false] - If ```true```, accept data payload as is without sanitization
17
+ * @property {boolean} [noResult=false] - If ```true```, returns nothing
18
+ * @property {boolean} [truncateString=true] - If ```true``` (default), string is truncated to its schema's ```maxLemngth```
19
+ * @property {boolean} [partial=true] - If ```true``` (default), only updated values are saved. Otherwise replace all existing values with given payload
20
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
21
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's schema
22
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
23
+ */
24
+
25
+ /**
26
+ * Update a record by it's ID and body payload
27
+ *
28
+ * Example:
29
+ * ```javascript
30
+ * const { recordUpdate } = this.app.dobo
31
+ * const { body } = {
32
+ * name: 'Republic of Indonesia',
33
+ * phoneCode: '+62'
34
+ * }
35
+ * const result = await recordUpdate('CdbCountry', 'ID', body)
36
+ * ```
37
+ *
38
+ * @method
39
+ * @memberof Dobo
40
+ * @async
41
+ * @instance
42
+ * @name recordUpdate
43
+ * @param {string} name - Model's name
44
+ * @param {(string|number)} id - Record's ID
45
+ * @param {Object} body - Body payload
46
+ * @param {TRecordUpdateOptions} [options={}]
47
+ * @returns {(TRecordUpdateResult|Object)} Returns updated record if ```options.dataOnly``` is set. {@link TRecordUpdateResult} otherwise
48
+ */
8
49
  async function update (name, id, input, opts = {}) {
9
- const { isSet } = this.lib.aneka
50
+ const { isSet } = this.app.lib.aneka
10
51
  const { runHook } = this.app.bajo
11
52
  const { clearModel } = this.cache ?? {}
12
- const { forOwn, find, cloneDeep, camelCase, omit, get } = this.lib._
53
+ const { forOwn, find, cloneDeep, camelCase, omit, get } = this.app.lib._
13
54
  delete opts.record
14
55
  const options = cloneDeep(omit(opts, ['req', 'reply']))
15
56
  options.req = opts.req
@@ -26,8 +67,8 @@ async function update (name, id, input, opts = {}) {
26
67
  let body = noSanitize ? input : await this.sanitizeBody({ body: input, schema, partial, strict: true, extFields })
27
68
  delete body.id
28
69
  if (!noHook) {
29
- await runHook(`${this.name}:beforeRecordUpdate`, name, id, body, options)
30
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordUpdate`, id, body, options)
70
+ await runHook(`${this.ns}:beforeRecordUpdate`, name, id, body, options)
71
+ await runHook(`${this.ns}.${camelCase(name)}:beforeRecordUpdate`, id, body, options)
31
72
  }
32
73
  if (!noValidation) body = await execValidation.call(this, { name, body, options, partial })
33
74
  if (!noCheckUnique) await checkUnique.call(this, { schema, body, id })
@@ -53,8 +94,8 @@ async function update (name, id, input, opts = {}) {
53
94
  record.oldData = await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden })
54
95
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
55
96
  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)
97
+ await runHook(`${this.ns}.${camelCase(name)}:afterRecordUpdate`, id, nbody, options, record)
98
+ await runHook(`${this.ns}:afterRecordUpdate`, name, id, nbody, options, record)
58
99
  }
59
100
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterUpdate', { schema, body: nbody, record })
60
101
  return dataOnly ? record.data : record
@@ -1,7 +1,23 @@
1
+ /**
2
+ * Update a record by payload's ID. If no record is found by given ID, a new one will be created instead.
3
+ *
4
+ * Missing ID in payload always results a new record creation.
5
+ * ```
6
+ *
7
+ * @method
8
+ * @memberof Dobo
9
+ * @async
10
+ * @instance
11
+ * @name recordUpsert
12
+ * @param {string} name - Model's name
13
+ * @param {Object} body - Body payload
14
+ * @param {TRecordUpsertOptions} [options={}]
15
+ * @returns {(TRecordUpdateResult|TRecordCreateResult|Object)} Returns updated/newly created record if ```options.dataOnly``` is set. {@link TRecordUpdateResult} or {@link TRecordCreateResult} otherwise
16
+ */
1
17
  async function upsert (name, input, opts = {}) {
2
18
  const { generateId } = this.app.bajo
3
- const { find } = this.lib._
4
- const { cloneDeep, omit, merge } = this.lib._
19
+ const { find } = this.app.lib._
20
+ const { cloneDeep, omit, merge } = this.app.lib._
5
21
  const { query, omitOnUpdate = [], omitOnCreate = [] } = opts
6
22
  const options = cloneDeep(omit(opts, ['req', 'reply', 'query', 'omitOnUpdate', 'omitOnCreate']))
7
23
  options.req = opts.req
@@ -1,8 +1,23 @@
1
+ /**
2
+ * Sanitize payload body against schema
3
+ *
4
+ * @method
5
+ * @memberof Dobo
6
+ * @async
7
+ * @param {Object} [options={}]
8
+ * @param {Object} [options.body={}]
9
+ * @param {Object} [options.schema={}]
10
+ * @param {boolean} [options.partial=false]
11
+ * @param {boolean} [options.strict=false]
12
+ * @param {Array} [options.extFields=[]]
13
+ * @returns {Object}
14
+ */
15
+
1
16
  async function sanitizeBody ({ body = {}, schema = {}, partial, strict, extFields = [] }) {
2
- const { isSet } = this.lib.aneka
3
- const { dayjs } = this.lib
17
+ const { isSet } = this.app.lib.aneka
18
+ const { dayjs } = this.app.lib
4
19
  const { callHandler } = this.app.bajo
5
- const { has, isString, isNumber, concat, isNaN } = this.lib._
20
+ const { has, isString, isNumber, concat, isNaN } = this.app.lib._
6
21
  const result = {}
7
22
  for (const p of concat(schema.properties, extFields)) {
8
23
  if (partial && !has(body, p.name)) continue
@@ -1,14 +1,27 @@
1
- function sanitizeDate (value, { input, output, silent = true } = {}) {
2
- const { dayjs } = this.lib
1
+ /**
2
+ * Sanitize value as a date/time value. Parse/format string using {@link https://day.js.org/docs/en/display/format|dayjs format}
3
+ *
4
+ * @method
5
+ * @memberof Dobo
6
+ * @param {(number|string)} value - Value to sanitize
7
+ * @param {Object} [options={}] - Options object
8
+ * @param {boolean} [options.silent=true] - If ```true``` (default) and value isn't valid, returns empty
9
+ * @param {string} [options.inputFormat] - If provided, parse value using this option
10
+ * @param {string} [options.outputFormat] - If not provided or ```native```, returns Javascript Date. Otherwise returns formatted date/time string
11
+ * @returns {(string|Date)}
12
+ */
13
+
14
+ function sanitizeDate (value, { inputFormat, outputFormat, silent = true } = {}) {
15
+ const { dayjs } = this.app.lib
3
16
  if (value === 0) return null
4
- if (!output) output = input
5
- const dt = dayjs(value, input)
17
+ if (!outputFormat) outputFormat = inputFormat
18
+ const dt = dayjs(value, inputFormat)
6
19
  if (!dt.isValid()) {
7
- if (silent) return -1
20
+ if (silent) return null
8
21
  throw this.error('invalidDate')
9
22
  }
10
- if (output === 'native' || !output) return dt.toDate()
11
- return dt.format(output)
23
+ if (outputFormat === 'native' || !outputFormat) return dt.toDate()
24
+ return dt.format(outputFormat)
12
25
  }
13
26
 
14
27
  export default sanitizeDate
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Sanitize id according it's schema
3
+ *
4
+ * @method
5
+ * @memberof Dobo
6
+ * @param {(number|string)} id
7
+ * @param {Object} schema
8
+ * @returns {(number|string)}
9
+ */
10
+
1
11
  function sanitizeId (id, schema) {
2
12
  const prop = schema.properties.find(p => p.name === 'id')
3
13
  if (prop.type === 'integer') id = parseInt(id)
@@ -7,15 +7,15 @@ async function aggregate (name, filter = {}, options = {}) {
7
7
  await this.modelExists(name, true)
8
8
  const { handler, schema, driver } = await resolveMethod.call(this, name, 'stat-aggregate', options)
9
9
  if (!noHook) {
10
- await runHook(`${this.name}:beforeStatAggregate`, name, aggregate, filter, options)
11
- await runHook(`${this.name}.${name}:beforeStatAggregate`, aggregate, filter, options)
10
+ await runHook(`${this.ns}:beforeStatAggregate`, name, aggregate, filter, options)
11
+ await runHook(`${this.ns}.${name}:beforeStatAggregate`, aggregate, filter, options)
12
12
  }
13
13
  filter.query = this.buildQuery({ filter, schema, options }) ?? {}
14
14
  filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
15
15
  const rec = await handler.call(this.app[driver.ns], { schema, filter, options })
16
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)
17
+ await runHook(`${this.ns}.${name}:afterStatAggregate`, aggregate, filter, options, rec)
18
+ await runHook(`${this.ns}:afterStatAggregate`, name, aggregate, filter, options, rec)
19
19
  }
20
20
  return dataOnly ? rec.data : rec
21
21
  }
@@ -12,13 +12,13 @@ async function histogram (name, filter = {}, options = {}) {
12
12
  filter.query = this.buildQuery({ filter, schema, options }) ?? {}
13
13
  filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
14
14
  if (!noHook) {
15
- await runHook(`${this.name}:beforeStatHistogram`, name, type, filter, options)
16
- await runHook(`${this.name}.${name}:beforeStatHistogram`, type, filter, options)
15
+ await runHook(`${this.ns}:beforeStatHistogram`, name, type, filter, options)
16
+ await runHook(`${this.ns}.${name}:beforeStatHistogram`, type, filter, options)
17
17
  }
18
18
  const rec = await handler.call(this.app[driver.ns], { schema, type, filter, options })
19
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)
20
+ await runHook(`${this.ns}.${name}:afterStatHistogram`, type, filter, options, rec)
21
+ await runHook(`${this.ns}:afterStatHistogram`, name, type, filter, options, rec)
22
22
  }
23
23
  return dataOnly ? rec.data : rec
24
24
  }