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
@@ -1,32 +0,0 @@
1
- const indexTypes = ['default', 'unique', 'primary', 'fulltext']
2
-
3
- async function genericPropSanitizer ({ prop, schema, driver }) {
4
- const { join } = this.app.bajo
5
- const { has, get, each } = this.app.lib._
6
- const { propType } = this.app.pluginClass.dobo
7
- const def = propType[prop.type]
8
- // detect from drivers
9
- if (prop.type === 'string') {
10
- def.minLength = prop.minLength ?? 0
11
- def.maxLength = prop.maxLength ?? 255
12
- if (has(prop, 'length')) def.maxLength = prop.length
13
- if (prop.required && def.minLength === 0) def.minLength = 1
14
- if (def.minLength > 0) prop.required = true
15
- }
16
- if (prop.autoInc && !['smallint', 'integer'].includes(prop.type)) delete prop.autoInc
17
- each(['minLength', 'maxLength', 'textType'], p => {
18
- if (!has(def, p)) {
19
- delete prop[p]
20
- return undefined
21
- }
22
- prop[p] = get(prop, p, get(this.config, `default.property.${prop.type}.${p}`, def[p]))
23
- if (def.values && !def.values.includes(prop[p])) {
24
- this.fatal('unsupportedAllowedChoices%s%s%s%s%s', p, prop[p], prop.name, schema.name, join(def.values))
25
- }
26
- })
27
- if (prop.index && !indexTypes.includes(prop.index.type)) {
28
- this.fatal('unsupportedIndexType%s%s%s%s', prop.index.type, prop.name, schema.name, join(indexTypes))
29
- }
30
- }
31
-
32
- export default genericPropSanitizer
@@ -1,16 +0,0 @@
1
- async function handleAttachmentUpload ({ action, name, id, options = {} } = {}) {
2
- const { getPluginDataDir } = this.app.bajo
3
- const { fs } = this.app.lib
4
- const { req, mimeType, stats, setFile, setField } = options
5
-
6
- name = this.attachmentPreCheck(name)
7
- if (!name) return
8
- if (action === 'remove') {
9
- const dir = `${getPluginDataDir(this.ns)}/attachment/${name}/${id}`
10
- await fs.remove(dir)
11
- return
12
- }
13
- return this.attachmentCopyUploaded(name, id, { req, mimeType, stats, setFile, setField })
14
- }
15
-
16
- export default handleAttachmentUpload
@@ -1,8 +0,0 @@
1
- async function connSanitizer (connection) {
2
- const { pick } = this.app.lib._
3
- const result = pick(connection, ['type', 'name', 'driver'])
4
- result.memory = true
5
- return result
6
- }
7
-
8
- export default connSanitizer
@@ -1,41 +0,0 @@
1
- let saving = false
2
-
3
- async function instantiate ({ connection, schemas, noRebuild }) {
4
- const { getPluginDataDir } = this.app.bajo
5
- const { fs } = this.app.lib
6
- const { pick } = this.app.lib._
7
- this.memDb = this.memDb ?? {}
8
- this.memDb.storage = this.memDb.storage ?? {}
9
- this.memDb.instances = this.memDb.instances ?? []
10
- const instance = pick(connection, ['name', 'type'])
11
- this.memDb.instances.push(instance)
12
- // if (noRebuild) return
13
- const pdir = `${getPluginDataDir(this.ns)}/memDb/data` // persistence dir
14
- fs.ensureDirSync(pdir)
15
- const persistence = []
16
- for (const schema of schemas) {
17
- this.memDb.storage[schema.name] = this.memDb.storage[schema.name] ?? [] // init empty model
18
- if (!schema.persistence) continue
19
- persistence.push(schema.name)
20
- // load if persistence
21
- const file = `${pdir}/${schema.name}.json`
22
- if (!fs.existsSync(file)) continue
23
- try {
24
- const data = fs.readFileSync(file, 'utf8')
25
- this.memDb.storage[schema.name] = JSON.parse(data)
26
- } catch (err) {
27
- this.fatal('cantLoad%s%s', schema.name, err.message)
28
- }
29
- }
30
- setInterval(() => {
31
- if (saving) return
32
- saving = true
33
- for (const item of persistence) {
34
- const data = this.memDb.storage[item]
35
- fs.writeFileSync(`${pdir}/${item}.json`, JSON.stringify(data), 'utf8')
36
- }
37
- saving = false
38
- }, this.config.memDb.persistence.syncPeriodDur)
39
- }
40
-
41
- export default instantiate
@@ -1,6 +0,0 @@
1
- async function clear ({ schema, options = {} }) {
2
- this.memDb.storage[schema.name].splice(0)
3
- return true
4
- }
5
-
6
- export default clear
@@ -1,5 +0,0 @@
1
- async function exists ({ schema, options = {} }) {
2
- this.memDb.storage[schema.name] = []
3
- }
4
-
5
- export default exists
@@ -1,5 +0,0 @@
1
- async function exists ({ schema, options = {} }) {
2
- this.memDb.storage[schema.name].splice(0)
3
- }
4
-
5
- export default exists
@@ -1,5 +0,0 @@
1
- async function exists ({ schema, options = {} }) {
2
- return true
3
- }
4
-
5
- export default exists
@@ -1,12 +0,0 @@
1
- import getRecord from './get.js'
2
-
3
- async function create ({ schema, body, options = {} }) {
4
- const { noResult } = options
5
- const exist = await getRecord.call(this, { schema, id: body.id, options: { thrownNotFound: false } })
6
- if (exist.data) throw this.error('recordExists%s%s', body.id, schema.name)
7
- this.memDb.storage[schema.name].push(body)
8
- if (noResult) return
9
- return { data: body }
10
- }
11
-
12
- export default create
@@ -1,20 +0,0 @@
1
- import { Query } from 'mingo'
2
-
3
- async function find ({ schema, filter = {}, options = {} }) {
4
- const { prepPagination } = this.app.dobo
5
- const { omit } = this.app.lib._
6
- const { limit, skip, sort, page } = await prepPagination(filter, schema)
7
- const criteria = filter.query ?? {}
8
- const q = new Query(criteria, { idKey: 'id' })
9
- let cursor = q.find(this.memDb.storage[schema.name])
10
- let count = 0
11
- if (options.count && !options.dataOnly) count = cursor.count()
12
- cursor = q.find(this.memDb.storage[schema.name])
13
- if (sort) cursor.sort(sort)
14
- if (!options.noLimit) cursor.skip(skip).limit(limit)
15
- let result = { data: cursor.all(), page, limit, count, pages: Math.ceil(count / limit) }
16
- if (!options.count) result = omit(result, ['count', 'pages'])
17
- return result
18
- }
19
-
20
- export default find
@@ -1,9 +0,0 @@
1
- async function get ({ schema, id, options = {} }) {
2
- const { thrownNotFound = true } = options
3
- const { find } = this.app.lib._
4
- const result = find(this.memDb.storage[schema.name], { id })
5
- if (!result && thrownNotFound) throw this.error('recordNotFound%s%s', id, schema.name, { statusCode: 404 })
6
- return { data: result }
7
- }
8
-
9
- export default get
@@ -1,13 +0,0 @@
1
- import getRecord from './get.js'
2
-
3
- async function remove ({ schema, id, options = {} }) {
4
- const { noResult } = options
5
- const { findIndex, pullAt } = this.app.lib._
6
- const rec = noResult ? undefined : await getRecord.call(this, { schema, id })
7
- const idx = findIndex(this.memDb.storage[schema.name], { id })
8
- pullAt(this.memDb.storage[schema.name], [idx])
9
- if (noResult) return
10
- return { oldData: rec.data }
11
- }
12
-
13
- export default remove
@@ -1,15 +0,0 @@
1
- import getRecord from './get.js'
2
-
3
- async function update ({ schema, id, body, options }) {
4
- const { noResult } = options
5
- const { findIndex, merge } = this.app.lib._
6
- const old = noResult ? undefined : await getRecord.call(this, { schema, id })
7
- const idx = findIndex(this.memDb.storage[schema.name], { id })
8
- const current = this.memDb.storage[schema.name][idx]
9
- this.memDb.storage[schema.name][idx] = merge(current, body)
10
- if (noResult) return
11
- const result = this.memDb.storage[schema.name][idx]
12
- return { oldData: old.data, data: result }
13
- }
14
-
15
- export default update
@@ -1,11 +0,0 @@
1
- import { Query } from 'mingo'
2
-
3
- async function count ({ schema, filter = {}, options = {} }) {
4
- const criteria = filter.query ?? {}
5
- const q = new Query(criteria, { idKey: 'id' })
6
- const cursor = q.find(this.memDb.storage[schema.name])
7
- const count = cursor.count()
8
- return { data: count }
9
- }
10
-
11
- export default count
@@ -1,25 +0,0 @@
1
- import addFixtures from '../add-fixtures.js'
2
-
3
- async function start () {
4
- const { filter, map } = this.app.lib._
5
-
6
- const conns = filter(this.connections, { type: 'dobo:memory' })
7
- const schemas = filter(this.schemas, s => {
8
- const names = map(conns, 'name')
9
- return names.includes(s.connection)
10
- })
11
- if (schemas.length === 0) return
12
- this.log.debug('addingFixtureToMemDb')
13
- for (const schema of schemas) {
14
- if (schema.persistence) {
15
- this.log.warn('addingMemoryPersistenceIgnored', schema.name)
16
- continue
17
- }
18
- let { success, error } = await addFixtures.call(this, schema.name)
19
- success = success ?? 0
20
- error = error ?? 0
21
- this.log.trace('- %s@%s (%d/%d)', schema.name, schema.connection, success, success + error)
22
- }
23
- }
24
-
25
- export default start
@@ -1,16 +0,0 @@
1
- async function mergeAttachmentInfo (rec, source, { mimeType, stats, fullPath }) {
2
- const { importPkg } = this.app.bajo
3
- const { fs } = this.app.lib
4
- const { pick } = this.app.lib._
5
- if (!this.app.waibu) return
6
- const mime = await importPkg('waibu:mime')
7
-
8
- if (mimeType) rec.mimeType = mime.getType(rec.file)
9
- if (fullPath) rec.fullPath = source
10
- if (stats) {
11
- const s = fs.statSync(source)
12
- rec.stats = pick(s, ['size', 'atime', 'ctime', 'mtime'])
13
- }
14
- }
15
-
16
- export default mergeAttachmentInfo
@@ -1,42 +0,0 @@
1
- async function multiRelRows ({ schema, records, options = {} }) {
2
- const { isSet } = this.app.lib.aneka
3
- const { uniq, find, map } = this.app.lib._
4
- const props = schema.properties.filter(p => isSet(p.rel) && !(options.hidden ?? []).includes(p.name))
5
- // const props = schema.properties.filter(p => isSet(p.rel))
6
- options.rels = options.rels ?? []
7
- if (props.length > 0) {
8
- for (const prop of props) {
9
- for (const key in prop.rel) {
10
- if (!((typeof options.rels === 'string' && ['*', 'all'].includes(options.rels)) || options.rels.includes(key))) continue
11
- const val = prop.rel[key]
12
- if (val.fields.length === 0) continue
13
- if (val.type !== 'one-to-one') continue
14
- const relschema = this.getSchema(val.schema)
15
- const matches = uniq(map(records, r => {
16
- let v = r[prop.name]
17
- if (val.propName === 'id') {
18
- const idField = find(relschema.properties, { name: 'id' })
19
- if (idField.type === 'integer') v = parseInt(v)
20
- }
21
- return v
22
- }))
23
- const query = {}
24
- query[val.propName] = { $in: matches }
25
- const relFilter = { query, limit: matches.length }
26
- const relOptions = { dataOnly: true, rels: [] }
27
- const results = await this.recordFind(relschema.name, relFilter, relOptions)
28
- const fields = [...val.fields]
29
- if (!fields.includes(prop.name)) fields.push(prop.name)
30
- for (const i in records) {
31
- records[i]._rel = records[i]._rel ?? {}
32
- const rec = records[i]
33
- const res = results.find(r => (r[val.propName] + '') === rec[prop.name] + '')
34
- if (res) records[i]._rel[key] = await this.pickRecord({ record: res, fields, schema: relschema })
35
- else records[i]._rel[key] = {}
36
- }
37
- }
38
- }
39
- }
40
- }
41
-
42
- export default multiRelRows
@@ -1,16 +0,0 @@
1
- async function resolveMethod (name, method, options = {}) {
2
- const { importModule } = this.app.bajo
3
- const { fs } = this.app.lib
4
- const { camelCase } = this.app.lib._
5
- const { schema, driver, connection } = this.getInfo(name)
6
- const [group, action] = method.split('-')
7
- if (!options.force && (schema.disabled ?? []).includes(action)) throw this.error('methodIsDisabled%s%s', camelCase(method), name)
8
- let file
9
- if (connection.name === 'memory') file = `${this.app[driver.ns].dir.pkg}/lib/mem-db/method/${group}/${action}.js`
10
- else file = `${this.app[driver.ns].dir.pkg}/extend/${this.ns}/method/${group}/${action}.js`
11
- if (!fs.existsSync(file)) throw this.error('methodUnsupported%s%s', camelCase(method), name)
12
- const handler = await importModule(file)
13
- return { handler, schema, driver, connection }
14
- }
15
-
16
- export default resolveMethod
@@ -1,198 +0,0 @@
1
- import genericPropSanitizer from './generic-prop-sanitizer.js'
2
-
3
- async function sanitizeFeature (item) {
4
- const { get, isPlainObject, mergeWith, isArray } = this.app.lib._
5
- for (const f of item.feature) {
6
- const feature = get(this.feature, f.name) // source from collectFeature
7
- if (!feature) this.fatal('unknownFeature%s%s', f.name, item.name)
8
- let [ns, path] = f.name.split('.')
9
- if (!path) ns = this.ns
10
- const input = await feature.call(this.app[ns], f)
11
- let props = input.properties
12
- if (isPlainObject(props)) props = [props]
13
- if (props.length > 0) item.properties.push(...props)
14
- item.globalRules = item.globalRules ?? []
15
- if (input.globalRules) {
16
- item.globalRules = mergeWith(item.globalRules, input.globalRules, (oval, sval) => {
17
- if (isArray(oval)) return oval.concat(sval)
18
- })
19
- }
20
- }
21
- }
22
-
23
- async function sanitizeIndexes (item) {
24
- const { generateId } = this.app.bajo
25
- for (const idx of item.indexes) {
26
- if (!idx.name) idx.name = `${(item.modelName ?? item.name).toUpperCase()}_${generateId()}`
27
- if (!(typeof idx.fields === 'string' || Array.isArray(idx.fields))) this.fatal('onlyAcceptFields%s%s', 'indexes', item.name)
28
- }
29
- }
30
-
31
- async function sanitizeFullText (item) {
32
- for (const f of item.fullText.fields ?? []) {
33
- const prop = item.properties.find(p => p.name === f)
34
- if (!prop) this.fatal('invalidFieldName%s%s', f, item.name)
35
- // if (prop.type !== 'text') fatal.call(this, 'Fulltext index only available for field type \'text\' in \'%s@%s\'', f, item.name)
36
- }
37
- }
38
-
39
- async function sanitizeSchema (items) {
40
- const { defaultsDeep } = this.app.lib.aneka
41
- const { freeze, fatal, importModule, join, breakNsPath, runHook } = this.app.bajo
42
- const { isEmpty, orderBy, map, keys, findIndex, find, each, isString, get, isPlainObject, camelCase, uniq, filter } = this.app.lib._
43
- const { propType } = this.app.pluginClass.dobo
44
- const propTypes = keys(propType)
45
- const schemas = []
46
- this.log.debug('loadingDbSchemas')
47
- for (const k in items) {
48
- this.log.trace('- %s (%d)', k, keys(items[k]).length)
49
- for (const f in items[k]) {
50
- const item = items[k][f]
51
- item.cacheable = item.cacheable ?? true
52
- await runHook(`dobo.${camelCase(item.name)}:beforeSanitizeSchema`, item)
53
- const conn = find(this.connections, { name: item.connection })
54
- if (!conn) fatal.call(this, 'Unknown connection \'%s@%s\'', item.name, item.connection)
55
- item.fullText = item.fullText ?? { fields: [] }
56
- item.indexes = item.indexes ?? []
57
- let { ns, path: type } = breakNsPath(conn.type)
58
- if (isEmpty(type)) type = conn.type
59
- const driver = find(this.drivers, { type, ns, driver: conn.driver })
60
- if (driver.lowerCaseModel) item.name = item.name.toLowerCase()
61
- let file = `${ns}:/extend/${this.ns}/lib/${type}/prop-sanitizer.js`
62
- let propSanitizer = await importModule(file)
63
- if (!propSanitizer) propSanitizer = genericPropSanitizer
64
- for (const idx in item.properties) {
65
- let prop = item.properties[idx]
66
- if (isString(prop)) {
67
- let [name, type, maxLength, index, required] = prop.split(':')
68
- if (!type) type = 'string'
69
- maxLength = maxLength ?? 255
70
- prop = { name, type }
71
- if (type === 'string') prop.maxLength = parseInt(maxLength) || undefined
72
- if (!isEmpty(index)) prop.index = { type: index === 'true' ? 'default' : index }
73
- prop.required = required === 'true'
74
- item.properties[idx] = prop
75
- } else {
76
- if (isString(prop.index)) prop.index = { type: prop.index }
77
- else if (prop.index === true) prop.index = { type: 'default' }
78
- }
79
- }
80
- const idx = findIndex(item.properties, { name: 'id' })
81
- if (idx === -1) item.properties.unshift(driver.idField)
82
- item.feature = item.feature ?? []
83
- await sanitizeFeature.call(this, item)
84
- item.disabled = item.disabled ?? []
85
- if (item.disabled === 'all') item.disabled = ['find', 'get', 'create', 'update', 'remove']
86
- else if (item.disabled === 'readonly') item.disabled = ['create', 'update', 'remove']
87
- for (const idx in item.properties) {
88
- let prop = item.properties[idx]
89
- if (!prop.type) {
90
- prop.type = 'string'
91
- prop.maxLength = 255
92
- }
93
- if (!propTypes.includes(prop.type)) {
94
- let success = false
95
- const feature = get(this.feature, isPlainObject(prop.type) ? prop.type.name : prop.type)
96
- if (feature) {
97
- let opts = { fieldName: prop.name }
98
- if (isPlainObject(prop.type)) opts = defaultsDeep(opts, prop.type)
99
- else opts.name = prop.type
100
- const feat = find(item.feature, opts)
101
- if (!feat) item.feature.push(opts)
102
- const input = await feature.call(this, opts)
103
- if (input.properties && input.properties.length > 0) {
104
- prop = input.properties[0]
105
- success = true
106
- }
107
- }
108
- if (!success) fatal.call(this, 'Unsupported property type \'%s@%s\' in \'%s\'', prop.type, prop.name, item.name)
109
- }
110
- if (prop.index) {
111
- if (prop.index === 'unique') prop.index = { type: 'unique' }
112
- else if (prop.index === 'fulltext') prop.index = { type: 'fulltext' }
113
- else if (prop.index === 'primary') prop.index = { type: 'primary' }
114
- else if (prop.index === true) prop.index = { type: 'default' }
115
- }
116
-
117
- await propSanitizer.call(this, { prop, schema: item, connection: conn, driver })
118
- if (prop.index && prop.index.type === 'primary' && prop.name !== 'id') fatal.call(this, 'Primary index should only be used for \'id\' field')
119
- if (prop.index && prop.index.type === 'fulltext') {
120
- item.fullText.fields.push(prop.name)
121
- prop.index.type = 'default'
122
- }
123
- item.properties[idx] = prop
124
- }
125
- await sanitizeIndexes.call(this, item)
126
- await sanitizeFullText.call(this, item)
127
- const all = []
128
- each(item.properties, p => {
129
- if (!all.includes(p.name)) all.push(p.name)
130
- else fatal.call(this, 'Field \'%s@%s\' should be used only once', p.name, item.name)
131
- })
132
- file = `${ns}:/extend/${this.ns}/lib/${type}/schema-sanitizer.js`
133
- const schemaSanitizer = await importModule(file)
134
- if (schemaSanitizer) await schemaSanitizer.call(this, { schema: item, connection: conn, driver })
135
- schemas.push(item)
136
- }
137
- }
138
- for (const i in schemas) {
139
- const schema = schemas[i]
140
- const sortables = []
141
- const hidden = []
142
- for (const i2 in schema.properties) {
143
- const prop = schema.properties[i2]
144
- if (prop.type !== 'string') delete prop.maxLength
145
- if (prop.rel) {
146
- for (const key in prop.rel) {
147
- let def = prop.rel[key]
148
- if (isString(def)) {
149
- const [rschema, rfield] = def.split(':')
150
- def = { schema: rschema, propName: rfield }
151
- }
152
- def.type = def.type ?? 'one-to-one'
153
- const rel = find(schemas, { name: def.schema })
154
- if (!rel) {
155
- fatal.call(this, 'No schema found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
156
- }
157
- const rprop = find(rel.properties, { name: def.propName })
158
- if (!rprop) fatal.call(this, 'No property found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
159
- def.fields = def.fields ?? '*'
160
- if (['*', 'all'].includes(def.fields)) {
161
- const relschema = find(schemas, { name: def.schema })
162
- def.fields = relschema.properties.map(p => p.name)
163
- }
164
- if (def.fields.length > 0 && !def.fields.includes('id')) def.fields.push('id')
165
- for (const f of def.fields) {
166
- const p = find(rel.properties, { name: f })
167
- if (!p) fatal.call(this, 'Unknown property for field \'%s\' in relationship \'%s@%s:%s\'', p, `${def.schema}:${def.propName}`, schema.name, prop.name)
168
- }
169
- prop.type = rprop.type
170
- if (rprop.type === 'string') prop.maxLength = rprop.maxLength
171
- else {
172
- delete prop.maxLength
173
- delete prop.minLength
174
- }
175
- prop.rel[key] = def
176
- }
177
- // TODO: propSanitizer must be called again
178
- }
179
- if (prop.hidden) hidden.push(prop.name)
180
- if (prop.index) sortables.push(prop.name)
181
- delete prop.hidden
182
- schema.properties[i2] = prop
183
- }
184
- // sortables
185
- each(schema.indexes, item => {
186
- sortables.push(...item.fields)
187
- })
188
- // hidden
189
- schema.hidden = uniq(filter((schema.hidden ?? []).concat(hidden), item => map(schema.properties, 'name').includes(item)))
190
- schema.sortables = sortables
191
- await runHook(`dobo.${camelCase(schema.name)}:afterSanitizeSchema`, schema)
192
- }
193
- this.schemas = orderBy(schemas, ['buildLevel', 'name'])
194
- freeze(this.schemas)
195
- this.log.debug('loadedSchemas%s', join(map(this.schemas, 'name')))
196
- }
197
-
198
- export default sanitizeSchema
@@ -1,38 +0,0 @@
1
- async function singleRelRows ({ schema, record, options = {} }) {
2
- const { isSet } = this.app.lib.aneka
3
- const { find } = this.app.lib._
4
- const props = schema.properties.filter(p => isSet(p.rel) && !(options.hidden ?? []).includes(p.name))
5
- const rels = {}
6
- options.rels = options.rels ?? []
7
- if (props.length > 0) {
8
- for (const prop of props) {
9
- for (const key in prop.rel) {
10
- if (!((typeof options.rels === 'string' && ['*', 'all'].includes(options.rels)) || options.rels.includes(key))) continue
11
- const val = prop.rel[key]
12
- if (val.fields.length === 0) continue
13
- const relschema = this.getSchema(val.schema)
14
- const relFilter = options.filter ?? {}
15
- const query = {}
16
- query[val.propName] = record[prop.name]
17
- if (val.propName === 'id') {
18
- const idField = find(relschema.properties, { name: 'id' })
19
- if (idField.type === 'integer') query[val.propName] = parseInt(query[val.propName])
20
- }
21
-
22
- relFilter.query = query
23
- const relOptions = { dataOnly: true, rels: [] }
24
- const results = await this.recordFind(relschema.name, relFilter, relOptions)
25
- const fields = [...val.fields]
26
- if (!fields.includes(prop.name)) fields.push(prop.name)
27
- const data = []
28
- for (const res of results) {
29
- data.push(await this.pickRecord({ record: res, fields, schema: relschema }))
30
- }
31
- rels[key] = ['one-to-one'].includes(val.type) ? data[0] : data
32
- }
33
- }
34
- }
35
- record._rel = rels
36
- }
37
-
38
- export default singleRelRows
@@ -1,34 +0,0 @@
1
- import path from 'path'
2
-
3
- async function copyUploaded (name, id, options = {}) {
4
- const { fs } = this.app.lib
5
- const { req, setField, setFile, mimeType, stats, silent = true } = options
6
- name = this.attachmentPreCheck(name)
7
- if (!name) {
8
- if (silent) return
9
- throw this.error('isMissing%s', this.t('field.name'))
10
- }
11
- if (!this.app.waibu) {
12
- if (silent) return
13
- throw this.error('missingPlugin%s', 'Waibu')
14
- }
15
- const { dir, files } = await this.app.waibu.getUploadedFiles(req.id, false, true)
16
- const result = []
17
- if (files.length === 0) return result
18
- for (const f of files) {
19
- let [field, ...parts] = path.basename(f).split('@')
20
- if (parts.length === 0) continue
21
- field = setField ?? field
22
- const file = setFile ?? parts.join('@')
23
- const opts = { source: f, field, file, mimeType, stats, req, silent: true }
24
- const rec = await this.attachmentCreate(name, id, opts)
25
- if (!rec) continue
26
- delete rec.dir
27
- result.push(rec)
28
- if (setField || setFile) break
29
- }
30
- fs.removeSync(dir)
31
- return result
32
- }
33
-
34
- export default copyUploaded
@@ -1,29 +0,0 @@
1
- import mergeAttachmentInfo from '../../lib/merge-attachment-info.js'
2
-
3
- async function create (name, id, options = {}) {
4
- const { fs } = this.app.lib
5
- const { isEmpty } = this.app.lib._
6
- name = this.attachmentPreCheck(name)
7
- if (!name) return
8
- const { source, field = 'file', file } = options
9
- if (isEmpty(file)) return
10
- if (!source) throw this.error('isMissing%s', this.t('field.source'))
11
- const baseDir = await this.attachmentGetPath(name, id, field, file, { dirOnly: true })
12
- const { fullPath, stats, mimeType, req } = options
13
-
14
- let dir = `${baseDir}/${field}`
15
- if ((field || '').endsWith('[]')) dir = `${baseDir}/${field.replace('[]', '')}`
16
- const dest = `${dir}/${file}`.replaceAll('//', '/')
17
- await fs.ensureDir(dir)
18
- await fs.copy(source, dest)
19
- const rec = {
20
- field: field === '' ? undefined : field,
21
- dir,
22
- file
23
- }
24
- await mergeAttachmentInfo.call(this, rec, dest, { mimeType, fullPath, stats })
25
- if (!options.silent && req && req.flash) req.flash('notify', req.t('attachmentUploaded'))
26
- return rec
27
- }
28
-
29
- export default create
@@ -1,27 +0,0 @@
1
- import mergeAttachmentInfo from '../../lib/merge-attachment-info.js'
2
-
3
- async function find (name, id, options = {}) {
4
- const { fastGlob, fs } = this.app.lib
5
- const { getPluginDataDir } = this.app.bajo
6
- name = this.attachmentPreCheck(name)
7
- if (!name) return
8
- const dir = `${getPluginDataDir(this.ns)}/attachment/${name}/${id}`
9
- if (!fs.existsSync(dir)) return []
10
- const files = await fastGlob(`${dir}/**/*`)
11
- const { fullPath, stats, mimeType } = options
12
- const recs = []
13
- for (const f of files) {
14
- const item = f.replace(dir, '')
15
- let [, field, file] = item.split('/')
16
- if (!file) {
17
- file = field
18
- field = null
19
- }
20
- const rec = { field, file }
21
- await mergeAttachmentInfo.call(this, rec, f, { mimeType, fullPath, stats })
22
- recs.push(rec)
23
- }
24
- return recs
25
- }
26
-
27
- export default find
@@ -1,12 +0,0 @@
1
- async function getPath (name, id, field, file, options = {}) {
2
- const { getPluginDataDir } = this.app.bajo
3
- const { pascalCase } = this.app.lib.aneka
4
- const { fs } = this.app.lib
5
- const dir = `${getPluginDataDir(this.ns)}/attachment/${pascalCase(name)}/${id}`
6
- if (options.dirOnly) return dir
7
- const path = field ? `${dir}/${field}/${file}` : `${dir}/${file}`
8
- if (!fs.existsSync(path)) throw this.error('notFound')
9
- return path
10
- }
11
-
12
- export default getPath