dobo 2.0.1 → 2.2.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 (194) hide show
  1. package/.github/FUNDING.yml +0 -0
  2. package/.github/workflows/repo-lockdown.yml +0 -0
  3. package/.jsdoc.conf.json +0 -0
  4. package/LICENSE +0 -0
  5. package/README.md +2 -2
  6. package/docs/Dobo.html +0 -0
  7. package/docs/data/search.json +0 -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 +0 -0
  12. package/docs/index.html +0 -0
  13. package/docs/index.js.html +0 -0
  14. package/docs/lib_collect-connections.js.html +0 -0
  15. package/docs/lib_collect-drivers.js.html +0 -0
  16. package/docs/lib_collect-features.js.html +0 -0
  17. package/docs/lib_collect-schemas.js.html +0 -0
  18. package/docs/lib_index.js.html +0 -0
  19. package/docs/method_model_create.js.html +0 -0
  20. package/docs/method_model_drop.js.html +0 -0
  21. package/docs/method_model_exists.js.html +0 -0
  22. package/docs/method_record_count.js.html +0 -0
  23. package/docs/method_record_create.js.html +0 -0
  24. package/docs/method_record_find-all.js.html +0 -0
  25. package/docs/method_record_find-one.js.html +0 -0
  26. package/docs/method_record_find.js.html +0 -0
  27. package/docs/method_record_get.js.html +0 -0
  28. package/docs/method_record_remove.js.html +0 -0
  29. package/docs/method_record_update.js.html +0 -0
  30. package/docs/method_record_upsert.js.html +0 -0
  31. package/docs/method_sanitize_body.js.html +0 -0
  32. package/docs/method_sanitize_date.js.html +0 -0
  33. package/docs/method_sanitize_id.js.html +0 -0
  34. package/docs/method_validate.js.html +0 -0
  35. package/docs/module-Lib.html +0 -0
  36. package/docs/scripts/core.js +476 -477
  37. package/docs/scripts/core.min.js +0 -0
  38. package/docs/scripts/resize.js +36 -36
  39. package/docs/scripts/search.js +105 -105
  40. package/docs/scripts/search.min.js +0 -0
  41. package/docs/scripts/third-party/Apache-License-2.0.txt +0 -0
  42. package/docs/scripts/third-party/fuse.js +1 -1
  43. package/docs/scripts/third-party/hljs-line-num-original.js +282 -285
  44. package/docs/scripts/third-party/hljs-line-num.js +1 -1
  45. package/docs/scripts/third-party/hljs-original.js +1195 -1202
  46. package/docs/scripts/third-party/hljs.js +1 -1
  47. package/docs/scripts/third-party/popper.js +1 -1
  48. package/docs/scripts/third-party/tippy.js +1 -1
  49. package/docs/scripts/third-party/tocbot.js +508 -509
  50. package/docs/scripts/third-party/tocbot.min.js +0 -0
  51. package/docs/static/bitcoin.jpeg +0 -0
  52. package/docs/static/home.md +0 -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 +0 -0
  56. package/docs/styles/clean-jsdoc-theme-dark.css +0 -0
  57. package/docs/styles/clean-jsdoc-theme-light.css +0 -0
  58. package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -0
  59. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -0
  60. package/docs/styles/clean-jsdoc-theme.min.css +0 -0
  61. package/extend/bajo/intl/en-US.json +66 -28
  62. package/extend/bajo/intl/id.json +55 -27
  63. package/extend/bajoCli/applet/clear-record.js +22 -0
  64. package/extend/bajoCli/applet/connection.js +0 -0
  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 +10 -17
  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 +9 -7
  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 +32 -54
  83. package/extend/dobo/feature/updated-at.js +14 -12
  84. package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +2 -6
  85. package/extend/waibuStatic/virtual.json +0 -0
  86. package/index.js +284 -371
  87. package/lib/collect-connections.js +49 -21
  88. package/lib/collect-drivers.js +19 -33
  89. package/lib/collect-features.js +24 -17
  90. package/lib/collect-models.js +321 -0
  91. package/lib/factory/action.js +161 -0
  92. package/lib/factory/connection.js +62 -0
  93. package/lib/factory/driver.js +372 -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/{method/record/find.js → lib/factory/model/find-record.js} +103 -115
  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 +56 -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/{method → lib/factory/model}/validate.js +38 -52
  122. package/lib/factory/model.js +150 -0
  123. package/lib/index.js +0 -0
  124. package/package.json +8 -4
  125. package/wiki/APPLETS.md +0 -0
  126. package/wiki/CHANGES.md +50 -0
  127. package/wiki/CONFIG.md +0 -0
  128. package/wiki/CONTRIBUTING.md +0 -0
  129. package/wiki/DEV-GUIDE.md +0 -0
  130. package/wiki/ECOSYSTEM.md +0 -0
  131. package/wiki/GETTING-STARTED.md +10 -10
  132. package/wiki/QUERY-LANGUAGE.md +0 -0
  133. package/wiki/USER-GUIDE.md +0 -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 -43
  137. package/extend/bajoCli/applet/record-find.js +0 -28
  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-schemas.js +0 -91
  146. package/lib/exec-feature-hook.js +0 -13
  147. package/lib/exec-validation.js +0 -21
  148. package/lib/generic-prop-sanitizer.js +0 -32
  149. package/lib/handle-attachment-upload.js +0 -16
  150. package/lib/mem-db/conn-sanitizer.js +0 -8
  151. package/lib/mem-db/instantiate.js +0 -41
  152. package/lib/mem-db/method/model/clear.js +0 -6
  153. package/lib/mem-db/method/model/create.js +0 -5
  154. package/lib/mem-db/method/model/drop.js +0 -5
  155. package/lib/mem-db/method/model/exists.js +0 -5
  156. package/lib/mem-db/method/record/create.js +0 -12
  157. package/lib/mem-db/method/record/find.js +0 -20
  158. package/lib/mem-db/method/record/get.js +0 -9
  159. package/lib/mem-db/method/record/remove.js +0 -13
  160. package/lib/mem-db/method/record/update.js +0 -15
  161. package/lib/mem-db/method/stat/count.js +0 -11
  162. package/lib/mem-db/start.js +0 -25
  163. package/lib/merge-attachment-info.js +0 -16
  164. package/lib/multi-rel-rows.js +0 -42
  165. package/lib/resolve-method.js +0 -16
  166. package/lib/sanitize-schema.js +0 -198
  167. package/lib/single-rel-rows.js +0 -38
  168. package/method/attachment/copy-uploaded.js +0 -34
  169. package/method/attachment/create.js +0 -29
  170. package/method/attachment/find.js +0 -27
  171. package/method/attachment/get-path.js +0 -12
  172. package/method/attachment/get.js +0 -12
  173. package/method/attachment/pre-check.js +0 -9
  174. package/method/attachment/remove.js +0 -11
  175. package/method/attachment/update.js +0 -7
  176. package/method/bulk/create.js +0 -46
  177. package/method/model/clear.js +0 -22
  178. package/method/model/create.js +0 -32
  179. package/method/model/drop.js +0 -31
  180. package/method/model/exists.js +0 -37
  181. package/method/record/clear.js +0 -24
  182. package/method/record/count.js +0 -66
  183. package/method/record/create.js +0 -111
  184. package/method/record/find-all.js +0 -41
  185. package/method/record/find-one.js +0 -70
  186. package/method/record/get.js +0 -89
  187. package/method/record/remove.js +0 -72
  188. package/method/record/update.js +0 -104
  189. package/method/record/upsert.js +0 -51
  190. package/method/sanitize/body.js +0 -85
  191. package/method/sanitize/date.js +0 -27
  192. package/method/sanitize/id.js +0 -17
  193. package/method/stat/aggregate.js +0 -23
  194. package/method/stat/histogram.js +0 -26
@@ -0,0 +1,29 @@
1
+ import { mergeAttachmentInfo } from './_util.js'
2
+ const action = 'findAttachment'
3
+
4
+ async function findAttachment (...args) {
5
+ if (!this.attachment) return
6
+ if (args.length === 0) return this.action(action, ...args)
7
+ const [id, opts = {}] = args
8
+ const { fastGlob, fs } = this.app.lib
9
+ const { getPluginDataDir } = this.app.bajo
10
+ const dir = `${getPluginDataDir(this.ns)}/attachment/${this.name}/${id}`
11
+ if (!fs.existsSync(dir)) return []
12
+ const files = await fastGlob(`${dir}/**/*`)
13
+ const { fullPath, stats, mimeType } = opts
14
+ const recs = []
15
+ for (const f of files) {
16
+ const item = f.replace(dir, '')
17
+ let [, fieldName, file] = item.split('/')
18
+ if (!file) {
19
+ file = fieldName
20
+ fieldName = null
21
+ }
22
+ const rec = { fieldName, file }
23
+ await mergeAttachmentInfo.call(this, rec, f, { mimeType, fullPath, stats })
24
+ recs.push(rec)
25
+ }
26
+ return recs
27
+ }
28
+
29
+ export default findAttachment
@@ -0,0 +1,19 @@
1
+ const action = 'findOneRecord'
2
+
3
+ async function findOneRecord (...args) {
4
+ if (args.length === 0) return this.action(action, ...args)
5
+ const [params = {}, opts = {}] = args
6
+ const { cloneDeep } = this.app.lib._
7
+ const { dataOnly = true } = opts
8
+ if (dataOnly) opts.count = false
9
+ const nFilter = cloneDeep(params)
10
+ const nOptions = cloneDeep(opts)
11
+ nOptions.count = false
12
+ nOptions.dataOnly = false
13
+ nFilter.limit = 1
14
+ const result = await this.findRecord(nFilter, nOptions)
15
+ const data = result.data[0]
16
+ return dataOnly ? data : { data }
17
+ }
18
+
19
+ export default findOneRecord
@@ -1,115 +1,103 @@
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
- /**
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
- */
68
- async function find (name, filter = {}, opts = {}) {
69
- const { isSet } = this.app.lib.aneka
70
- const { runHook } = this.app.bajo
71
- const { get, set } = this.cache ?? {}
72
- const { cloneDeep, camelCase, omit } = this.app.lib._
73
- delete opts.records
74
- const options = cloneDeep(omit(opts, ['req', 'reply']))
75
- options.req = opts.req
76
- options.reply = opts.reply
77
- options.dataOnly = options.dataOnly ?? true
78
- let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
79
- options.count = options.count ?? false
80
- options.dataOnly = false
81
- await this.modelExists(name, true)
82
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-find', options)
83
- if (!schema.cacheable) noCache = true
84
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
85
- if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
86
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
87
- if (!noHook) {
88
- await runHook(`${this.ns}:beforeRecordFind`, name, filter, options)
89
- await runHook(`${this.ns}.${camelCase(name)}:beforeRecordFind`, filter, options)
90
- }
91
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFind', { schema, filter, options })
92
- if (get && !noCache && !options.records) {
93
- const cachedResult = await get({ model: name, filter, options })
94
- if (cachedResult) {
95
- cachedResult.cached = true
96
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records: cachedResult })
97
- return dataOnly ? cachedResult.data : cachedResult
98
- }
99
- }
100
- const records = options.records ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
101
- delete options.records
102
- if (isSet(options.rels)) await multiRelRows.call(this, { schema, records: records.data, options })
103
- for (const idx in records.data) {
104
- records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
105
- }
106
- if (!noHook) {
107
- await runHook(`${this.ns}.${camelCase(name)}:afterRecordFind`, filter, options, records)
108
- await runHook(`${this.ns}:afterRecordFind`, name, filter, options, records)
109
- }
110
- if (set && !noCache) await set({ model: name, filter, options, records })
111
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
112
- return dataOnly ? records.data : records
113
- }
114
-
115
- export default find
1
+ import { getFilterAndOptions, execHook, execModelHook, getMultiRefs } from './_util.js'
2
+ const action = 'findRecord'
3
+
4
+ /**
5
+ * @typedef {Object} TRecordFilter
6
+ * @see Dobo#recordFind
7
+ * @see Dobo#recordFindOne
8
+ * @see Dobo#recordFindAll
9
+ * @property {(string|Object)} [query={}] - Query definition. See {@tutorial query-language} for more
10
+ * @property {number} limit - Max number of records per page
11
+ * @property {number} page - Which page is the returned records currently at
12
+ * @property {number} skip - Records to skip
13
+ * @property {TRecordSort} sort - Sort order info
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} TRecordFindResult
18
+ * @see Dobo#recordFind
19
+ * @see Dobo#recordFindAll
20
+ * @see Dobo#recordGet
21
+ * @property {Array.<Object>} data - Array of returned records
22
+ * @property {boolean} success - Whether operation is successfull or failed
23
+ * @property {number} page - Which page is the returned records currently at
24
+ * @property {number} limit - Max number of records per page
25
+ * @property {number} count - Total number of records returned
26
+ * @property {number} pages - Total number of pages returned
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} TRecordFindOptions
31
+ * @see Dobo#recordFind
32
+ * @see Dobo#recordFindOne
33
+ * @see Dobo#recordFindAll
34
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
35
+ * @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
36
+ * @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
37
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
38
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
39
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
40
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
41
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
42
+ */
43
+
44
+ /**
45
+ * Find records by model's name and given filter
46
+ *
47
+ * Example: find records from model **CdbCountry** where its id is 'ID' or 'MY',
48
+ * sorted by ```name``` in ascending order and return only its ```id```, ```name``` and ```iso3```
49
+ * ```javascript
50
+ * const { recordFind } = this.app.dobo
51
+ * const query = { id: { $in: ['ID', 'MY'] } }
52
+ * const sort = { name: 1 }
53
+ * const fields = ['id', 'name', 'iso3']
54
+ * const result = await recordFind('CdbCountry', { query, sort }, { fields })
55
+ * ```
56
+ *
57
+ * @method
58
+ * @memberof Dobo
59
+ * @async
60
+ * @instance
61
+ * @name recordFind
62
+ * @param {string} name - Model's name
63
+ * @param {Object} [filter={}] - Filter object
64
+ * @param {TRecordFindOptions} [options={}]
65
+ * @returns {(TRecordFindResult|Array.<Object>)} Return ```array``` of records if ```options.dataOnly``` is set. {@link TRecordFindResult} otherwise
66
+ */
67
+ async function findRecord (...args) {
68
+ if (args.length === 0) return this.action(action, ...args)
69
+ const [params = {}, opts = {}] = args
70
+ const { isSet } = this.app.lib.aneka
71
+ const { runHook } = this.app.bajo
72
+ const { dataOnly = true } = opts
73
+ const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
74
+ if (dataOnly) options.count = false
75
+ let { noResultSanitizer, noCache } = options
76
+ if (!this.cacheable || !options.record) noCache = true
77
+ if (!noCache) {
78
+ try {
79
+ await runHook('cache:getByFilter', this, filter)
80
+ } catch (err) {
81
+ if (err.code === 'CACHED_RESULT') {
82
+ const result = err.payload
83
+ result.cache = true
84
+ return dataOnly ? result.data : result
85
+ }
86
+ }
87
+ }
88
+ await execHook.call(this, 'beforeFindRecord', filter, options)
89
+ await execModelHook.call(this, 'beforeFindRecord', filter, options)
90
+ const result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
91
+ if (!noResultSanitizer) {
92
+ for (const idx in result.data) {
93
+ result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
94
+ }
95
+ }
96
+ if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
97
+ await execModelHook.call(this, 'afterFindRecord', filter, result, options)
98
+ await execHook.call(this, 'afterFindRecord', filter, result, options)
99
+ if (!noCache) await runHook('cache:setByFilter', this, filter, result)
100
+ return dataOnly ? result.data : result
101
+ }
102
+
103
+ export default findRecord
@@ -0,0 +1,15 @@
1
+ const action = 'getAttachment'
2
+
3
+ async function getAttachment (...args) {
4
+ if (!this.attachment) return
5
+ if (args.length === 0) return this.action(action, ...args)
6
+ let [id, fieldName, file, opts = {}] = args
7
+ const { find } = this.app.lib._
8
+ const all = await this.findAttachment(id, opts)
9
+ if (fieldName === 'null') fieldName = null
10
+ const data = find(all, { fieldName, file })
11
+ if (!data) throw this.error('notFound', { statusCode: 404 })
12
+ return data
13
+ }
14
+
15
+ export default getAttachment
@@ -0,0 +1,79 @@
1
+ import { getFilterAndOptions, execHook, execModelHook, getSingleRef } from './_util.js'
2
+ const action = 'getRecord'
3
+
4
+ /**
5
+ * @typedef {Object} TRecordGetResult
6
+ * @see Dobo#recordGet
7
+ * @see Dobo#recordFindOne
8
+ * @property {Object} data - Returned record
9
+ * @property {boolean} success - Whether operation is successfull or failed
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} TRecordgetFilterAndOptions
14
+ * @see Dobo#recordGet
15
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
16
+ * @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
17
+ * @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
18
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
19
+ * @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
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 model
22
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
23
+ */
24
+
25
+ /**
26
+ * Get record by model's name and record ID
27
+ *
28
+ * Example:
29
+ * ```javascript
30
+ * const { recordGet } = this.app.dobo
31
+ * const fields = ['id', 'name', 'iso3']
32
+ * const result = await recordGet('CdbCountry', 'ID', { fields })
33
+ * ```
34
+ *
35
+ * @method
36
+ * @memberof Dobo
37
+ * @async
38
+ * @instance
39
+ * @name recordGet
40
+ * @param {string} name - Model's name
41
+ * @param {(string|number)} - Record's ID
42
+ * @param {TRecordgetFilterAndOptions} [options={}]
43
+ * @returns {(TRecordGetResult|Object)} Return record's ```object``` if ```options.dataOnly``` is set. {@link TRecordGetResult} otherwise
44
+ */
45
+ async function getRecord (...args) {
46
+ if (args.length === 0) return this.action(action, ...args)
47
+ let [id, opts = {}] = args
48
+ const { isEmpty } = this.app.lib._
49
+ const { isSet } = this.app.lib.aneka
50
+ const { runHook } = this.app.bajo
51
+ const { dataOnly = true } = opts
52
+ const { options } = await getFilterAndOptions.call(this, null, opts, action)
53
+ let { noResultSanitizer, noCache } = options
54
+ if (!this.cacheable || !options.record) noCache = true
55
+ id = this.sanitizeId(id)
56
+ await execHook.call(this, 'beforeGetRecord', id, options)
57
+ await execModelHook.call(this, 'beforeGetRecord', id, options)
58
+ if (!noCache) {
59
+ try {
60
+ await runHook('cache:getById', this, id)
61
+ } catch (err) {
62
+ if (err.code === 'CACHED_RESULT') {
63
+ const result = err.payload
64
+ result.cache = true
65
+ return dataOnly ? result.data : result
66
+ }
67
+ }
68
+ }
69
+ const result = options.record ?? (await this.driver._getRecord(this, id, options)) ?? {}
70
+ if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
71
+ if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
72
+ if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
73
+ await execModelHook.call(this, 'afterGetRecord', id, result.data, options)
74
+ await execHook.call(this, 'afterGetRecord', id, result, options)
75
+ if (!noCache) await runHook('cache:setById', this, id, result)
76
+ return dataOnly ? result.data : result
77
+ }
78
+
79
+ export default getRecord
@@ -0,0 +1,37 @@
1
+ import path from 'path'
2
+ const action = 'listAttachment'
3
+
4
+ async function listAttachment (...args) {
5
+ if (!this.attachment) return
6
+ if (args.length === 0) return this.action(action, ...args)
7
+ const [params = {}, opts = {}] = args
8
+ const { map, kebabCase } = this.app.lib._
9
+ const { importPkg, getPluginDataDir } = this.app.bajo
10
+ const mime = await importPkg('waibu:mime')
11
+ const { fastGlob } = this.app.lib
12
+
13
+ const { id = '*', fieldName = '*', file = '*' } = params
14
+ const { uriEncoded = true } = opts
15
+ const root = `${getPluginDataDir('dobo')}/attachment`
16
+ let pattern = `${root}/${this.name}/${id}/${fieldName}/${file}`
17
+ if (uriEncoded) pattern = pattern.split('/').map(p => decodeURI(p)).join('/')
18
+ return map(await fastGlob(pattern), f => {
19
+ const mimeType = mime.getType(path.extname(f)) ?? ''
20
+ const fullPath = f.replace(root, '')
21
+ const row = {
22
+ file: f,
23
+ fileName: path.basename(fullPath),
24
+ fullPath,
25
+ mimeType,
26
+ params: { model: this.name, id, fieldName, file }
27
+ }
28
+ if (this.app.waibuMpa) {
29
+ const { routePath } = this.app.waibu
30
+ const [, _model, _id, _fieldName, _file] = fullPath.split('/')
31
+ row.url = routePath(`dobo:/attachment/${kebabCase(_model)}/${_id}/${_fieldName}/${_file}`)
32
+ }
33
+ return row
34
+ })
35
+ }
36
+
37
+ export default listAttachment
@@ -1,67 +1,69 @@
1
- import path from 'path'
2
-
3
- async function addFixture (name, { spinner } = {}) {
4
- const { resolvePath, readConfig, eachPlugins, getPluginDataDir } = this.app.bajo
5
- const { isEmpty, isArray, isString } = this.app.lib._
6
- const { fs } = this.app.lib
7
- const { schema, connection } = this.getInfo(name)
8
- if (connection.proxy) {
9
- this.log.warn('proxiedConnBound%s', schema.name)
10
- return
11
- }
12
- const result = { success: 0, failed: 0 }
13
- const base = path.basename(schema.file, path.extname(schema.file))
14
- // original
15
- const pattern = resolvePath(`${path.dirname(schema.file)}/../fixture/${base}.*`)
16
- let items = await readConfig(pattern, { ns: schema.ns, ignoreError: true })
17
- if (isEmpty(items)) items = []
18
- // override
19
- const overrides = await readConfig(`${this.app.main.dir.pkg}/extend/dobo/override/${schema.ns}/fixture/${base}.*`, { ns: this.ns, ignoreError: true })
20
-
21
- if (isArray(overrides) && !isEmpty(overrides)) items = overrides
22
- // extend
23
- await eachPlugins(async function ({ dir }) {
24
- const { ns } = this
25
- const extend = await readConfig(`${dir}/extend/dobo/extend/${schema.ns}/fixture/${base}.*`, { ns, ignoreError: true })
26
- if (isArray(extend) && !isEmpty(extend)) items.push(...extend)
27
- })
28
- if (isEmpty(items)) return result
29
- const opts = { noHook: true, noCache: true }
30
- for (const item of items) {
31
- try {
32
- for (const k in item) {
33
- const v = item[k]
34
- if (typeof v === 'string' && v.slice(0, 2) === '?:') {
35
- let [, model, field, ...query] = v.split(':')
36
- if (!field) field = 'id'
37
- const recs = await this.recordFind(model, { query: query.join(':') }, opts)
38
- item[k] = (recs[0] ?? {})[field]
39
- }
40
- if (v === null) item[k] = undefined
41
- }
42
- const resp = await this.recordCreate(schema.name, item, { force: true })
43
- if (isArray(item._attachments) && item._attachments.length > 0) {
44
- for (let att of item._attachments) {
45
- if (isString(att)) att = { field: 'file', file: att }
46
- const fname = path.basename(att.file)
47
- if (fs.existsSync(att.file)) {
48
- const dest = `${getPluginDataDir(schema.ns)}/${resp.id}/${att.field}/${fname}`
49
- try {
50
- fs.copySync(att.file, dest)
51
- } catch (err) {}
52
- }
53
- }
54
- }
55
- result.success++
56
- if (spinner) spinner.setText('recordsAdded%s%d%d', schema.name, result.success, items.length)
57
- } catch (err) {
58
- console.log(err)
59
- err.model = schema.name
60
- if (this.app.applet) this.print.fail(this.validationErrorMessage(err))
61
- result.failed++
62
- }
63
- }
64
- return result
65
- }
66
-
67
- export default addFixture
1
+ import path from 'path'
2
+
3
+ async function loadFixtures ({ spinner } = {}) {
4
+ const { readConfig, eachPlugins, getPluginDataDir } = this.app.bajo
5
+ const { resolvePath } = this.app.lib.aneka
6
+ const { isEmpty, isArray, isString } = this.app.lib._
7
+ const { fs } = this.app.lib
8
+ if (this.connection.proxy) {
9
+ this.log.warn('proxiedConnBound%s', this.name)
10
+ return
11
+ }
12
+ const result = { success: 0, failed: 0 }
13
+ const base = path.basename(this.file, path.extname(this.file))
14
+ // original
15
+ const pattern = resolvePath(`${path.dirname(this.file)}/../fixture/${base}.*`)
16
+ let items = await readConfig(pattern, { ns: this.plugin.ns, ignoreError: true })
17
+ if (isEmpty(items)) items = []
18
+ // override
19
+ const overrides = await readConfig(`${this.app.main.dir.pkg}/extend/dobo/override/${this.plugin.ns}/fixture/${base}.*`, { ns: this.plugin.ns, ignoreError: true })
20
+
21
+ if (isArray(overrides) && !isEmpty(overrides)) items = overrides
22
+ // extend
23
+ const me = this
24
+ await eachPlugins(async function ({ dir }) {
25
+ const { ns } = this
26
+ const extend = await readConfig(`${dir}/extend/dobo/extend/${me.plugin.ns}/fixture/${base}.*`, { ns, ignoreError: true })
27
+ if (isArray(extend) && !isEmpty(extend)) items.push(...extend)
28
+ })
29
+ if (isEmpty(items)) return result
30
+ const opts = { noHook: true, noCache: true }
31
+ for (const item of items) {
32
+ try {
33
+ for (const k in item) {
34
+ const v = item[k]
35
+ if (typeof v === 'string' && v.slice(0, 2) === '?:') {
36
+ let [, model, field, ...query] = v.split(':')
37
+ if (!field) field = 'id'
38
+ const ref = this.app.dobo.getModel(model)
39
+ const recs = await ref.findRecord({ query: query.join(':') }, opts)
40
+ item[k] = (recs[0] ?? {})[field]
41
+ }
42
+ if (v === null) item[k] = undefined
43
+ }
44
+ const resp = await this.createRecord(item, { force: true })
45
+ if (isArray(item._attachments) && item._attachments.length > 0) {
46
+ for (let att of item._attachments) {
47
+ if (isString(att)) att = { field: 'file', file: att }
48
+ const fname = path.basename(att.file)
49
+ if (fs.existsSync(att.file)) {
50
+ const dest = `${getPluginDataDir(this.plugin.ns)}/${resp.id}/${att.field}/${fname}`
51
+ try {
52
+ fs.copySync(att.file, dest)
53
+ } catch (err) {}
54
+ }
55
+ }
56
+ }
57
+ result.success++
58
+ if (spinner) spinner.setText('recordsAdded%s%d%d', this.name, result.success, items.length)
59
+ } catch (err) {
60
+ console.log(err)
61
+ err.model = this.name
62
+ if (this.app.applet) this.plugin.print.fail(this.app.dobo.validationErrorMessage(err))
63
+ result.failed++
64
+ }
65
+ }
66
+ return result
67
+ }
68
+
69
+ export default loadFixtures
@@ -0,0 +1,15 @@
1
+ import { getAttachmentPath } from './_util.js'
2
+ const action = 'removeAttachment'
3
+
4
+ async function removeAttachment (...args) {
5
+ if (!this.attachment) return
6
+ if (args.length === 0) return this.action(action, ...args)
7
+ const [id, fieldName, file, opts = {}] = args
8
+ const { fs } = this.app.lib
9
+ const path = await getAttachmentPath.call(this, id, fieldName, file)
10
+ const { req } = opts
11
+ await fs.remove(path)
12
+ if (!opts.noFlash && req && req.flash) req.flash('notify', req.t('attachmentRemoved'))
13
+ }
14
+
15
+ export default removeAttachment
@@ -0,0 +1,59 @@
1
+ import { getFilterAndOptions, execHook, execModelHook, getSingleRef, handleReq } from './_util.js'
2
+ const action = 'removeRecord'
3
+
4
+ /**
5
+ * @typedef {Object} TRecordRemoveOptions
6
+ * @see Dobo#recordRemove
7
+ * @property {boolean} [dataOnly=true] - If ```true``` (default) returns deleted record. Otherwise {@link TRecordRemoveResult}
8
+ * @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
9
+ * @property {boolean} [noModelHook=false] - If ```true```, no model's feature hook will be executed
10
+ * @property {boolean} [noResult=false] - If ```true```, returns nothing
11
+ * @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
12
+ * @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
13
+ * @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
14
+ */
15
+
16
+ /**
17
+ * Remove existing record by it's ID. All attachments bound to this record will also be removed forever.
18
+ *
19
+ * Example:
20
+ * ```javascript
21
+ * const { recordRemove } = this.app.dobo
22
+ * const result = await recordRemove('CdbCountry', 'ID')
23
+ * ```
24
+ *
25
+ * @method
26
+ * @memberof Model
27
+ * @async
28
+ * @instance
29
+ * @name removeRecord
30
+ * @param {(string|number)} id - Record's ID
31
+ * @param {TRecordRemoveOptions} [options={}]
32
+ * @returns {(TRecordRemoveResult|Object)} Return the removed record if ```options.dataOnly``` is set. {@link TRecordRemoveResult} otherwise
33
+ */
34
+ async function removeRecord (...args) {
35
+ if (args.length === 0) return this.action(action, ...args)
36
+ let [id, opts = {}] = args
37
+ const { isSet } = this.app.lib.aneka
38
+ const { runHook } = this.app.bajo
39
+ const { dataOnly = true } = opts
40
+ const { options } = await getFilterAndOptions.call(this, null, opts, action)
41
+ const { noResult, noResultSanitizer } = options
42
+ id = this.sanitizeId(id)
43
+ await execHook.call(this, 'beforeRemoveRecord', id, options)
44
+ await execModelHook.call(this, 'beforeRemoveRecord', id, options)
45
+ const result = options.record ?? (await this.driver._removeRecord(this, id, options)) ?? {}
46
+ if (noResult) {
47
+ await runHook('cache:clear', this, 'remove', id)
48
+ return
49
+ }
50
+ if (!noResultSanitizer) result.oldData = await this.sanitizeRecord(result.oldData, options)
51
+ if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
52
+ await handleReq.call(this, result.oldData.id, 'removed', options)
53
+ await execModelHook.call(this, 'afterRemoveRecord', id, result, options)
54
+ await execHook.call(this, 'afterRemoveRecord', id, result, options)
55
+ await runHook('cache:clear', this, 'remove', id, result)
56
+ return dataOnly ? result.oldData : result
57
+ }
58
+
59
+ export default removeRecord