dobo 2.0.1 → 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 (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 +291 -366
  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 +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/{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 +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/{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 +46 -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,22 @@
1
+ async function model (path, ...args) {
2
+ const { importPkg } = this.app.bajo
3
+ const { isEmpty, map, find } = this.app.lib._
4
+ const { getOutputFormat, writeOutput } = this.app.bajoCli
5
+ const select = await importPkg('bajoCli:@inquirer/select')
6
+ const format = getOutputFormat()
7
+ if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
8
+ let name = args[0]
9
+ if (isEmpty(name)) {
10
+ const choices = map(this.models, s => ({ value: s.name }))
11
+ name = await select({
12
+ message: this.print.buildText('selectModel'),
13
+ choices
14
+ })
15
+ }
16
+ const result = find(this.models, { name })
17
+ if (!result) return this.print.fail('cantFind%s%s', this.t('model'), name, { exit: this.app.applet })
18
+ this.print.info('done')
19
+ await writeOutput(result, path, format)
20
+ }
21
+
22
+ export default model
@@ -0,0 +1,91 @@
1
+ async function modelRebuild (path, ...args) {
2
+ const { importPkg } = this.app.bajo
3
+ const { outmatch } = this.app.lib
4
+ const { isEmpty, map, trim, without } = this.app.lib._
5
+ const [input, confirm, boxen] = await importPkg('bajoCli:@inquirer/input',
6
+ 'bajoCli:@inquirer/confirm', 'bajoCli:boxen')
7
+ const models = map(this.models, 'name')
8
+ let names = args.join(' ')
9
+ if (isEmpty(models)) return this.print.fail('notFound%s', 'model', { exit: this.app.applet })
10
+ if (isEmpty(names)) {
11
+ names = await input({
12
+ message: this.print.buildText('enterModelName'),
13
+ default: '*'
14
+ })
15
+ }
16
+ const isMatch = outmatch(map(names.split(' '), m => trim(m)))
17
+ names = models.filter(isMatch)
18
+ if (names.length === 0) return this.print.fail('No model matched', true, { exit: this.app.applet })
19
+ console.log(boxen(names.join(' '), { title: this.t('model%d', names.length), padding: 0.5, borderStyle: 'round' }))
20
+ const answer = await confirm({
21
+ message: this.print.buildText('modelsWillBeRebuiltContinue'),
22
+ default: false
23
+ })
24
+ if (!answer) return this.print.fail('aborted', { exit: this.app.applet })
25
+ /*
26
+ const conns = []
27
+ for (const s of names) {
28
+ const { connection } = this.getInfo(s)
29
+ if (!conns.includes(connection.name)) conns.push(connection.name)
30
+ }
31
+ */
32
+ await this.start('all')
33
+ const result = { succed: 0, failed: 0, skipped: 0 }
34
+ const skipped = []
35
+ for (const s of names) {
36
+ const model = this.getModel(s)
37
+ const spin = this.print.spinner().start('rebuilding%s', model.name)
38
+ if (model.driver.memory) {
39
+ spin.warn('memoryDbSkipped%s', model.name)
40
+ continue
41
+ }
42
+ const exists = await model.exists({ spinner: spin })
43
+ if (exists) {
44
+ if (this.app.bajo.config.force) {
45
+ try {
46
+ await model.drop({ spinner: spin })
47
+ spin.setText('modelDropped%s', model.name)
48
+ } catch (err) {
49
+ spin.fail('errorDroppingModel%s%s', model.name, err.message)
50
+ result.failed++
51
+ continue
52
+ }
53
+ } else {
54
+ spin.fail('modelExistsNeedForce%s', model.name)
55
+ result.failed++
56
+ continue
57
+ }
58
+ }
59
+ try {
60
+ await model.build({ spinner: spin })
61
+ spin.succeed('modelCreated%s', model.name)
62
+ result.succed++
63
+ } catch (err) {
64
+ if (this.app.bajo.config.log.applet) console.error(err)
65
+ spin.fail('errorCreatingModel%s%s', model.name, err.message)
66
+ result.failed++
67
+ }
68
+ }
69
+ this.print.info('succeedFailSkip%d%d%d', result.succed, result.failed, result.skipped)
70
+ if (result.failed > 0) this.print.fatal('cantContinueAddFixture')
71
+ for (const s of without(names, ...skipped)) {
72
+ const model = this.getModel(s)
73
+ const spin = this.print.spinner().start('addingFixture%s', model.name)
74
+ if (model.driver.memory) {
75
+ spin.warn('memoryDbSkipped%s', model.name)
76
+ continue
77
+ }
78
+ try {
79
+ const fixture = await model.loadFixtures({ spinner: spin })
80
+ spin.succeed('fixtureAdded%s%s%s', model.name, fixture.success, fixture.failed)
81
+ result.succed++
82
+ } catch (err) {
83
+ if (this.app.bajo.config.log.applet) console.error(err)
84
+ spin.fail('errorAddingFixture%s%s', model.name, err.message)
85
+ result.failed++
86
+ }
87
+ }
88
+ this.app.exit()
89
+ }
90
+
91
+ export default modelRebuild
@@ -0,0 +1,27 @@
1
+ import postProcess from './lib/post-process.js'
2
+
3
+ async function removeRecord (path, ...args) {
4
+ const { importPkg } = this.app.bajo
5
+ const { isEmpty, map } = this.app.lib._
6
+ const { parseKvString } = this.app.lib.aneka
7
+ const [input, select] = await importPkg('bajoCli:@inquirer/input', 'bajoCli:@inquirer/select')
8
+ if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
9
+ let [model, id, options] = args
10
+ options = isEmpty(options) ? {} : parseKvString(options)
11
+ if (isEmpty(model)) {
12
+ model = await select({
13
+ message: this.print.buildText('selectModel'),
14
+ choices: map(this.models, s => ({ value: s.name }))
15
+ })
16
+ }
17
+ if (isEmpty(id + '')) {
18
+ id = await input({
19
+ message: this.print.buildText('enterRecordId'),
20
+ validate: text => isEmpty(text) ? this.t('idIsRequired') : true
21
+ })
22
+ }
23
+ await postProcess.call(this, { noConfirmation: options.noConfirmation, handler: 'removeRecord', params: [model, id, options], path, processMsg: 'Removing record' })
24
+ this.app.exit()
25
+ }
26
+
27
+ export default removeRecord
@@ -0,0 +1,44 @@
1
+ import postProcess from './lib/post-process.js'
2
+
3
+ async function updateRecord (path, ...args) {
4
+ const { importPkg } = this.app.bajo
5
+ const { isEmpty, map } = this.app.lib._
6
+ const { parseKvString } = this.app.lib.aneka
7
+ const [input, select, boxen] = await importPkg('bajoCli:@inquirer/input',
8
+ 'bajoCli:@inquirer/select', 'bajoCli:boxen')
9
+ if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
10
+ let [model, id, body, options] = args
11
+ options = isEmpty(options) ? {} : parseKvString(options)
12
+ if (isEmpty(model)) {
13
+ model = await select({
14
+ message: this.print.buildText('selectModel'),
15
+ choices: map(this.models, s => ({ value: s.name }))
16
+ })
17
+ }
18
+ if (isEmpty(id + '')) {
19
+ id = await input({
20
+ message: this.print.buildText('enterRecordId'),
21
+ validate: text => isEmpty(text) ? this.t('idIsRequired') : true
22
+ })
23
+ }
24
+ if (isEmpty(body)) {
25
+ body = await input({
26
+ message: this.print.buildText('enterPayload'),
27
+ validate: text => {
28
+ if (isEmpty(text)) return this.t('payloadRequired')
29
+ return true
30
+ }
31
+ })
32
+ }
33
+ let payload
34
+ try {
35
+ payload = body[0] === '{' ? JSON.parse(body) : parseKvString(body)
36
+ } catch (err) {
37
+ return this.print.fail('invalidPayloadSyntax', { exit: this.app.applet })
38
+ }
39
+ console.log(boxen(JSON.stringify(payload, null, 2), { title: model, padding: 0.5, borderStyle: 'round' }))
40
+ await postProcess.call(this, { noConfirmation: options.noConfirmation, handler: 'updateRecord', params: [model, id, payload, options], path, processMsg: 'Updating record' })
41
+ this.app.exit()
42
+ }
43
+
44
+ export default updateRecord
File without changes
@@ -0,0 +1,170 @@
1
+ import { Query } from 'mingo'
2
+
3
+ async function memoryDriverFactory () {
4
+ const { DoboDriver } = this.app.baseClass
5
+ const { findIndex, pullAt, omit, has } = this.app.lib._
6
+ const { defaultsDeep } = this.app.lib.aneka
7
+
8
+ class DoboMemoryDriver extends DoboDriver {
9
+ constructor (plugin, name, options) {
10
+ super(plugin, name, options)
11
+ this.idGenerator = 'ulid'
12
+ this.saving = true
13
+ this.memory = true
14
+ this.autoSave = []
15
+ this.storage = {}
16
+ this.support = {
17
+ propType: {
18
+ object: true,
19
+ array: true
20
+ }
21
+ }
22
+ }
23
+
24
+ async _loadFromFile (model, dir) {
25
+ const { fs } = this.app.lib
26
+ this.autoSave.push(model.name)
27
+ const file = `${dir}/${model.name}.json`
28
+ if (!fs.existsSync(file)) return
29
+ try {
30
+ const data = fs.readFileSync(file, 'utf8')
31
+ this.storage[model.name] = JSON.parse(data)
32
+ } catch (err) {
33
+ this.fatal('cantLoad%s%s', model.name, err.message)
34
+ }
35
+ }
36
+
37
+ async sanitizeConnection (conn) {
38
+ await super.sanitizeConnection(conn)
39
+ conn.memory = true
40
+ }
41
+
42
+ async connect (connection, noRebuild) {
43
+ const conn = this.plugin.getConnection('memory')
44
+ const models = this.plugin.getModelsByConnection(conn.name)
45
+ const { getPluginDataDir } = this.app.bajo
46
+ const { fs } = this.app.lib
47
+ const pdir = `${getPluginDataDir(this.plugin.ns)}/memDb/data` // persistence dir
48
+ fs.ensureDirSync(pdir)
49
+ conn.autoSave = conn.autoSave ?? []
50
+ for (const model of models) {
51
+ this.storage[model.name] = this.storage[model.name] ?? [] // init empty model
52
+ if (conn.autoSave.includes(model.name)) await this._loadFromFile(model, pdir)
53
+ await model.loadFixtures()
54
+ }
55
+ if (conn.autoSave.length === 0) return
56
+ setInterval(() => {
57
+ if (!this.saving) return
58
+ this.saving = true
59
+ for (const item of conn.autoSave) {
60
+ const data = this.storage[item]
61
+ fs.writeFileSync(`${pdir}/${item}.json`, JSON.stringify(data), 'utf8')
62
+ }
63
+ this.saving = false
64
+ }, this.plugin.config.memDb.autoSaveDur)
65
+ }
66
+
67
+ async _getOldRecord (model, id, options = {}) {
68
+ const idx = findIndex(this.storage[model.name], { _id: id })
69
+ const oldData = this.storage[model.name][idx]
70
+ return { idx, oldData }
71
+ }
72
+
73
+ async modelExists (model, options = {}) {
74
+ return { data: has(this.storage, model.name) }
75
+ }
76
+
77
+ async buildModel (model, options = {}) {
78
+ if (has(this.storage, model.name)) throw this.plugin.error('exist%s%s', this.plugin.t('model'), model.name)
79
+ this.storage[model.name] = []
80
+ if (options.noResult) return
81
+ return { data: true }
82
+ }
83
+
84
+ async dropModel (model, options = {}) {
85
+ if (!has(this.storage, model.name)) throw this.plugin.error('notFound%s%s', this.plugin.t('model'), model.name)
86
+ delete this.storage[model.name]
87
+ if (options.noResult) return
88
+ return { data: true }
89
+ }
90
+
91
+ async createRecord (model, body = {}, options = {}) {
92
+ this.storage[model.name].push(body)
93
+ return { data: body }
94
+ }
95
+
96
+ async getRecord (model, id, options = {}) {
97
+ const { oldData: data } = await this._getOldRecord(model, id)
98
+ return { data }
99
+ }
100
+
101
+ async updateRecord (model, id, body = {}, options = {}) {
102
+ const { idx, oldData } = await this._getOldRecord(model, id)
103
+ const data = defaultsDeep(body, oldData)
104
+ this.storage[model.name][idx] = data
105
+ return { oldData, data }
106
+ }
107
+
108
+ async removeRecord (model, id, options = {}) {
109
+ const { idx, oldData } = await this._getOldRecord(model, id)
110
+ pullAt(this.storage[model.name], idx)
111
+ return { oldData }
112
+ }
113
+
114
+ async clearRecord (model, options = {}) {
115
+ this.storage[model.name] = []
116
+ if (options.noResult) return
117
+ return { data: true }
118
+ }
119
+
120
+ async findRecord (model, filter = {}, options = {}) {
121
+ const { limit, skip, sort, page } = filter
122
+ const { data: count = 0 } = await this.countRecord(model, filter, options)
123
+ const cursor = this._getCursor(model, filter)
124
+ if (sort) cursor.sort(sort)
125
+ if (!options.noLimit) cursor.skip(skip).limit(limit)
126
+ let result = { data: cursor.all(), page, limit, count, pages: Math.ceil(count / limit) }
127
+ if (!options.count) result = omit(result, ['count', 'pages'])
128
+ return result
129
+ }
130
+
131
+ async findAllRecord (model, filter = {}, options = {}) {
132
+ const { sort } = filter
133
+ const { data: count = 0 } = await this.countRecord(model, filter, options)
134
+ const cursor = this._getCursor(model, filter)
135
+ if (sort) cursor.sort(sort)
136
+ let result = { data: cursor.all(), count }
137
+ if (!options.count) result = omit(result, ['count'])
138
+ return result
139
+ }
140
+
141
+ async countRecord (model, filter = {}, options = {}) {
142
+ const cursor = this._getCursor(model, filter)
143
+ const data = cursor.all().length
144
+ return { data }
145
+ }
146
+
147
+ async createAggregate (model, filter = {}, params = {}, options = {}) {
148
+ const item = await this.findAllRecord(model, filter, options)
149
+ const result = this.app.dobo.calcAggregate({ data: item.data, ...params })
150
+ return { data: result }
151
+ }
152
+
153
+ async createHistogram (model, filter = {}, params = {}, options = {}) {
154
+ const item = await this.findAllRecord(model, filter, options)
155
+ const result = this.app.dobo.calcHistogram({ data: item.data, ...params })
156
+ return { data: result }
157
+ }
158
+
159
+ _getCursor (model, filter) {
160
+ const criteria = filter.query ?? {}
161
+ const q = new Query(criteria, { idKey: '_id' })
162
+ return q.find(this.storage[model.name])
163
+ }
164
+ }
165
+
166
+ this.app.baseClass.DoboMemoryDriver = DoboMemoryDriver
167
+ return DoboMemoryDriver
168
+ }
169
+
170
+ export default memoryDriverFactory
@@ -1,18 +1,20 @@
1
1
  async function createdAt (opts = {}) {
2
2
  opts.fieldName = opts.fieldName ?? 'createdAt'
3
+ opts.noOverwrite = opts.noOverwrite ?? false
3
4
  return {
4
- properties: {
5
+ properties: [{
5
6
  name: opts.fieldName,
6
7
  type: 'datetime',
7
8
  index: true
8
- },
9
- hook: {
10
- beforeCreate: async function ({ body }) {
9
+ }],
10
+ hooks: [{
11
+ name: 'beforeCreateRecord',
12
+ handler: async function (body, options) {
11
13
  const { isSet } = this.app.lib.aneka
12
- const now = new Date()
13
- if (opts.overwrite || !isSet(body[opts.fieldName])) body[opts.fieldName] = now
14
+ if (opts.noOverwrite) body[opts.fieldName] = new Date()
15
+ else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
14
16
  }
15
- }
17
+ }]
16
18
  }
17
19
  }
18
20
 
File without changes
@@ -0,0 +1,30 @@
1
+ async function beforeRemoveRecord (id, opts) {
2
+ const { get } = this.app.lib._
3
+ const record = await this.driver.getRecord(this, id)
4
+ const immutable = get(record.data, opts.fieldName)
5
+ if (immutable) throw this.plugin.error('recordImmutable%s%s', id, this.name, { statusCode: 423 })
6
+ }
7
+
8
+ async function immutable (opts = {}) {
9
+ opts.fieldName = opts.fieldName ?? '_immutable'
10
+ return {
11
+ properties: {
12
+ name: opts.fieldName,
13
+ type: 'boolean',
14
+ hidden: true
15
+ },
16
+ hooks: [{
17
+ name: 'beforeUpdateRecord',
18
+ handler: async function (id, body, options) {
19
+ await beforeRemoveRecord.call(this, id, opts)
20
+ }
21
+ }, {
22
+ name: 'beforeRemoveRecord',
23
+ handler: async function (id, options) {
24
+ await beforeRemoveRecord.call(this, id, opts)
25
+ }
26
+ }]
27
+ }
28
+ }
29
+
30
+ export default immutable
File without changes
@@ -1,4 +1,4 @@
1
- async function beforeFind ({ filter = {}, options }, opts) {
1
+ async function beforeFindRecord ({ filter = {} }, opts) {
2
2
  filter.query = filter.query ?? {}
3
3
  const { isEmpty, set } = this.app.lib._
4
4
  const q = { $and: [] }
@@ -10,75 +10,53 @@ async function beforeFind ({ filter = {}, options }, opts) {
10
10
  filter.query = q
11
11
  }
12
12
 
13
- async function afterFind ({ records }, opts) {
14
- for (const rec of records.data) {
15
- delete rec[opts.fieldName]
16
- }
17
- }
18
-
19
- async function afterGet ({ schema, id, record }, opts) {
13
+ async function afterGetRecord ({ id, record = {} }, opts) {
20
14
  const { isEmpty } = this.app.lib._
21
- if (!isEmpty(record.data[opts.fieldName])) throw this.error('recordNotFound%s%s', id, schema.name, { statusCode: 404 })
22
- delete record.data[opts.fieldName]
15
+ if (!isEmpty(record.data[opts.fieldName])) throw this.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
23
16
  }
24
17
 
25
- async function beforeCreate ({ body }, opts) {
18
+ async function beforeCreateRecord ({ body = {} }, opts) {
26
19
  delete body[opts.fieldName]
27
20
  }
28
21
 
29
- async function afterCreate ({ record }, opts) {
30
- delete record.data[opts.fieldName]
31
- if (record.oldData) delete record.oldData[opts.fieldName]
32
- }
33
-
34
22
  async function removedAt (opts = {}) {
35
- opts.fieldName = opts.fieldName ?? 'removedAt'
23
+ opts.fieldName = opts.fieldName ?? '_removedAt'
36
24
  return {
37
25
  properties: {
38
26
  name: opts.fieldName,
39
27
  type: 'datetime',
40
- index: true
28
+ index: true,
29
+ hidden: true
41
30
  },
42
- hook: {
43
- beforeFind: async function ({ filter, options }) {
44
- await beforeFind.call(this, { filter, options }, opts)
45
- },
46
- beforeFindOne: async function ({ filter, options }) {
47
- await beforeFind.call(this, { filter, options }, opts)
48
- },
49
- afterFind: async function ({ records }) {
50
- await afterFind.call(this, { records }, opts)
51
- },
52
- afterFindOne: async function ({ record }) {
53
- await afterGet.call(this, { record }, opts)
54
- },
55
- afterGet: async function ({ record }) {
56
- await afterGet.call(this, { record }, opts)
57
- },
58
- beforeCreate: async function ({ body }) {
59
- await beforeCreate.call(this, { body }, opts)
60
- },
61
- afterCreate: async function ({ record }) {
62
- await afterCreate.call(this, { record }, opts)
63
- },
64
- beforeUpdate: async function ({ schema, id, body, options }) {
65
- await beforeCreate.call(this, { body }, opts)
66
- },
67
- afterUpdate: async function ({ record }) {
68
- await afterCreate.call(this, { record }, opts)
69
- },
70
- beforeRemove: async function ({ schema, id, options }) {
71
- const { recordUpdate, recordGet } = this.app.dobo
72
- await recordGet(schema.name, id, options)
31
+ hooks: [{
32
+ name: 'beforeFindRecord',
33
+ handler: async function (filter, options) {
34
+ await beforeFindRecord.call(this, { filter, options }, opts)
35
+ }
36
+ }, {
37
+ name: 'afterGetRecord',
38
+ handler: async function (id, record, options) {
39
+ await afterGetRecord.call(this, { record }, opts)
40
+ }
41
+ }, {
42
+ name: 'beforeCreateRecord',
43
+ handler: async function (body, options) {
44
+ await beforeCreateRecord.call(this, { body }, opts)
45
+ }
46
+ }, {
47
+ name: 'beforeUpdateRecord',
48
+ handler: async function (id, body, options) {
49
+ await beforeCreateRecord.call(this, { body }, opts)
50
+ }
51
+ }, {
52
+ name: 'beforeRemoveRecord',
53
+ handler: async function (id, options) {
73
54
  const { set } = this.app.lib._
74
55
  const body = set({}, opts.fieldName, new Date())
75
- const record = await recordUpdate(schema.name, id, body, { dataOnly: false, noValidation: true, noFeatureHook: true })
56
+ const record = await this.driver.recordUpdate(this, id, body, { noResult: false })
76
57
  options.record = { oldData: record.oldData }
77
- },
78
- afterRemove: async function ({ record }) {
79
- delete record.oldData[opts.fieldName]
80
58
  }
81
- }
59
+ }]
82
60
  }
83
61
  }
84
62
 
@@ -1,24 +1,26 @@
1
1
  async function updatedAt (opts = {}) {
2
+ const { isSet } = this.app.lib.aneka
2
3
  opts.fieldName = opts.fieldName ?? 'updatedAt'
3
- opts.overwrite = opts.overwrite ?? true
4
+ opts.noOverwrite = opts.noOverwrite ?? false
4
5
  return {
5
6
  properties: {
6
7
  name: opts.fieldName,
7
8
  type: 'datetime',
8
9
  index: true
9
10
  },
10
- hook: {
11
- beforeCreate: async function ({ body }) {
12
- const { isSet } = this.app.lib.aneka
13
- const now = new Date()
14
- if (opts.overwrite || !isSet(body[opts.fieldName])) body[opts.fieldName] = now
15
- },
16
- beforeUpdate: async function ({ body }) {
17
- const { isSet } = this.app.lib.aneka
18
- const now = new Date()
19
- if (opts.overwrite || !isSet(body[opts.fieldName])) body[opts.fieldName] = now
11
+ hooks: [{
12
+ name: 'beforeCreateRecord',
13
+ handler: async function (body, options) {
14
+ if (opts.noOverwrite) body[opts.fieldName] = new Date()
15
+ else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
20
16
  }
21
- }
17
+ }, {
18
+ name: 'beforeUpdateRecord',
19
+ handler: async function (id, body, options) {
20
+ if (opts.noOverwrite) body[opts.fieldName] = new Date()
21
+ else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
22
+ }
23
+ }]
22
24
  }
23
25
  }
24
26
 
@@ -5,12 +5,8 @@ async function attachment (req, reply) {
5
5
  const { pascalCase } = this.app.lib.aneka
6
6
  const { routePath } = this.app.waibu
7
7
  const { fs } = this.app.lib
8
- const items = await this.listAttachments({
9
- model: req.params.model,
10
- id: req.params.id,
11
- field: req.params.field,
12
- file: '*'
13
- })
8
+ const mdl = this.app.dobo.getModel(req.params.model)
9
+ const items = await mdl.listAttachments(req.params.id, req.params.field, '*')
14
10
  let item = req.params.file === '_first' ? items[0] : undefined
15
11
  if (!item) {
16
12
  item = find(items, i => {
File without changes