dobo 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +45 -0
  4. package/LICENSE +1 -1
  5. package/README.md +38 -19
  6. package/docs/Dobo.html +26 -0
  7. package/docs/data/search.json +1 -0
  8. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  9. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  10. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  11. package/docs/global.html +7 -0
  12. package/docs/index.html +3 -0
  13. package/docs/index.js.html +578 -0
  14. package/docs/lib_collect-connections.js.html +39 -0
  15. package/docs/lib_collect-drivers.js.html +52 -0
  16. package/docs/lib_collect-features.js.html +36 -0
  17. package/docs/lib_collect-schemas.js.html +94 -0
  18. package/docs/lib_index.js.html +6 -0
  19. package/docs/method_model_create.js.html +35 -0
  20. package/docs/method_model_drop.js.html +34 -0
  21. package/docs/method_model_exists.js.html +40 -0
  22. package/docs/method_record_count.js.html +69 -0
  23. package/docs/method_record_create.js.html +114 -0
  24. package/docs/method_record_find-all.js.html +44 -0
  25. package/docs/method_record_find-one.js.html +73 -0
  26. package/docs/method_record_find.js.html +118 -0
  27. package/docs/method_record_get.js.html +92 -0
  28. package/docs/method_record_remove.js.html +75 -0
  29. package/docs/method_record_update.js.html +107 -0
  30. package/docs/method_record_upsert.js.html +54 -0
  31. package/docs/method_sanitize_body.js.html +88 -0
  32. package/docs/method_sanitize_date.js.html +30 -0
  33. package/docs/method_sanitize_id.js.html +20 -0
  34. package/docs/method_validate.js.html +249 -0
  35. package/docs/module-Lib.html +3 -0
  36. package/docs/scripts/core.js +725 -0
  37. package/docs/scripts/core.min.js +23 -0
  38. package/docs/scripts/resize.js +90 -0
  39. package/docs/scripts/search.js +265 -0
  40. package/docs/scripts/search.min.js +6 -0
  41. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  42. package/docs/scripts/third-party/fuse.js +9 -0
  43. package/docs/scripts/third-party/hljs-line-num-original.js +366 -0
  44. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  45. package/docs/scripts/third-party/hljs-original.js +5164 -0
  46. package/docs/scripts/third-party/hljs.js +1 -0
  47. package/docs/scripts/third-party/popper.js +5 -0
  48. package/docs/scripts/third-party/tippy.js +1 -0
  49. package/docs/scripts/third-party/tocbot.js +671 -0
  50. package/docs/scripts/third-party/tocbot.min.js +1 -0
  51. package/docs/static/bitcoin.jpeg +0 -0
  52. package/docs/static/home.md +25 -0
  53. package/docs/static/logo-ecosystem.png +0 -0
  54. package/docs/static/logo.png +0 -0
  55. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  56. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  57. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  58. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  59. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  60. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  61. package/extend/bajo/intl/en-US.json +69 -30
  62. package/extend/bajo/intl/id.json +58 -29
  63. package/extend/bajoCli/applet/clear-record.js +22 -0
  64. package/extend/bajoCli/applet/connection.js +5 -5
  65. package/extend/bajoCli/applet/count-record.js +27 -0
  66. package/extend/bajoCli/applet/create-aggregate.js +33 -0
  67. package/extend/bajoCli/applet/create-histogram.js +33 -0
  68. package/extend/bajoCli/applet/create-record.js +39 -0
  69. package/extend/bajoCli/applet/find-record.js +27 -0
  70. package/extend/bajoCli/applet/get-record.js +27 -0
  71. package/extend/bajoCli/applet/lib/post-process.js +25 -26
  72. package/extend/bajoCli/applet/model.js +22 -0
  73. package/extend/bajoCli/applet/rebuild-model.js +91 -0
  74. package/extend/bajoCli/applet/remove-record.js +27 -0
  75. package/extend/bajoCli/applet/update-record.js +44 -0
  76. package/extend/bajoCli/applet.js +0 -0
  77. package/extend/dobo/driver/memory.js +170 -0
  78. package/extend/dobo/feature/created-at.js +10 -8
  79. package/extend/dobo/feature/dt.js +0 -0
  80. package/extend/dobo/feature/immutable.js +30 -0
  81. package/extend/dobo/feature/int-id.js +0 -0
  82. package/extend/dobo/feature/removed-at.js +35 -57
  83. package/extend/dobo/feature/updated-at.js +14 -12
  84. package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +5 -9
  85. package/extend/waibuStatic/virtual.json +0 -0
  86. package/index.js +420 -337
  87. package/lib/collect-connections.js +60 -21
  88. package/lib/collect-drivers.js +29 -35
  89. package/lib/collect-features.js +40 -0
  90. package/lib/collect-models.js +319 -0
  91. package/lib/factory/action.js +161 -0
  92. package/lib/factory/connection.js +62 -0
  93. package/lib/factory/driver.js +358 -0
  94. package/lib/factory/feature.js +33 -0
  95. package/lib/factory/model/_util.js +402 -0
  96. package/lib/factory/model/build.js +15 -0
  97. package/lib/factory/model/clear-record.js +17 -0
  98. package/lib/factory/model/count-record.js +17 -0
  99. package/lib/factory/model/create-aggregate.js +17 -0
  100. package/lib/factory/model/create-attachment.js +29 -0
  101. package/lib/factory/model/create-histogram.js +17 -0
  102. package/lib/factory/model/create-record.js +35 -0
  103. package/lib/factory/model/drop.js +15 -0
  104. package/lib/factory/model/exists.js +21 -0
  105. package/lib/factory/model/find-all-record.js +71 -0
  106. package/lib/factory/model/find-attachment.js +29 -0
  107. package/lib/factory/model/find-one-record.js +19 -0
  108. package/lib/factory/model/find-record.js +103 -0
  109. package/lib/factory/model/get-attachment.js +15 -0
  110. package/lib/factory/model/get-record.js +79 -0
  111. package/lib/factory/model/list-attachment.js +37 -0
  112. package/lib/{add-fixtures.js → factory/model/load-fixtures.js} +69 -67
  113. package/lib/factory/model/remove-attachment.js +15 -0
  114. package/lib/factory/model/remove-record.js +59 -0
  115. package/lib/factory/model/sanitize-body.js +62 -0
  116. package/lib/factory/model/sanitize-id.js +7 -0
  117. package/lib/factory/model/sanitize-record.js +26 -0
  118. package/lib/factory/model/update-attachment.js +9 -0
  119. package/lib/factory/model/update-record.js +81 -0
  120. package/lib/factory/model/upsert-record.js +95 -0
  121. package/lib/factory/model/validate.js +232 -0
  122. package/lib/factory/model.js +150 -0
  123. package/lib/index.js +3 -0
  124. package/package.json +45 -36
  125. package/wiki/APPLETS.md +57 -0
  126. package/wiki/CHANGES.md +46 -0
  127. package/wiki/CONFIG.md +25 -0
  128. package/wiki/CONTRIBUTING.md +5 -0
  129. package/wiki/DEV-GUIDE.md +1 -0
  130. package/wiki/ECOSYSTEM.md +20 -0
  131. package/wiki/GETTING-STARTED.md +166 -0
  132. package/{docs/query-language.md → wiki/QUERY-LANGUAGE.md} +0 -0
  133. package/wiki/USER-GUIDE.md +1 -0
  134. package/extend/bajoCli/applet/model-clear.js +0 -11
  135. package/extend/bajoCli/applet/model-rebuild.js +0 -101
  136. package/extend/bajoCli/applet/record-create.js +0 -41
  137. package/extend/bajoCli/applet/record-find.js +0 -27
  138. package/extend/bajoCli/applet/record-get.js +0 -24
  139. package/extend/bajoCli/applet/record-remove.js +0 -24
  140. package/extend/bajoCli/applet/record-update.js +0 -47
  141. package/extend/bajoCli/applet/schema.js +0 -22
  142. package/extend/bajoCli/applet/stat-count.js +0 -24
  143. package/lib/build-bulk-action.js +0 -12
  144. package/lib/check-unique.js +0 -39
  145. package/lib/collect-feature.js +0 -25
  146. package/lib/collect-schemas.js +0 -83
  147. package/lib/exec-feature-hook.js +0 -13
  148. package/lib/exec-validation.js +0 -21
  149. package/lib/generic-prop-sanitizer.js +0 -31
  150. package/lib/handle-attachment-upload.js +0 -16
  151. package/lib/mem-db/conn-sanitizer.js +0 -8
  152. package/lib/mem-db/instantiate.js +0 -41
  153. package/lib/mem-db/method/model/clear.js +0 -6
  154. package/lib/mem-db/method/model/create.js +0 -5
  155. package/lib/mem-db/method/model/drop.js +0 -5
  156. package/lib/mem-db/method/model/exists.js +0 -5
  157. package/lib/mem-db/method/record/create.js +0 -12
  158. package/lib/mem-db/method/record/find.js +0 -20
  159. package/lib/mem-db/method/record/get.js +0 -9
  160. package/lib/mem-db/method/record/remove.js +0 -13
  161. package/lib/mem-db/method/record/update.js +0 -15
  162. package/lib/mem-db/method/stat/count.js +0 -11
  163. package/lib/mem-db/start.js +0 -25
  164. package/lib/merge-attachment-info.js +0 -16
  165. package/lib/multi-rel-rows.js +0 -42
  166. package/lib/resolve-method.js +0 -16
  167. package/lib/sanitize-schema.js +0 -197
  168. package/lib/single-rel-rows.js +0 -38
  169. package/method/attachment/copy-uploaded.js +0 -34
  170. package/method/attachment/create.js +0 -29
  171. package/method/attachment/find.js +0 -27
  172. package/method/attachment/get-path.js +0 -12
  173. package/method/attachment/get.js +0 -12
  174. package/method/attachment/pre-check.js +0 -9
  175. package/method/attachment/remove.js +0 -11
  176. package/method/attachment/update.js +0 -7
  177. package/method/bulk/create.js +0 -46
  178. package/method/model/clear.js +0 -22
  179. package/method/model/create.js +0 -19
  180. package/method/model/drop.js +0 -19
  181. package/method/model/exists.js +0 -24
  182. package/method/record/clear.js +0 -24
  183. package/method/record/count.js +0 -44
  184. package/method/record/create.js +0 -71
  185. package/method/record/find-all.js +0 -25
  186. package/method/record/find-one.js +0 -56
  187. package/method/record/find.js +0 -52
  188. package/method/record/get.js +0 -47
  189. package/method/record/remove.js +0 -41
  190. package/method/record/update.js +0 -63
  191. package/method/record/upsert.js +0 -35
  192. package/method/sanitize/body.js +0 -70
  193. package/method/sanitize/date.js +0 -14
  194. package/method/sanitize/id.js +0 -7
  195. package/method/stat/aggregate.js +0 -23
  196. package/method/stat/histogram.js +0 -26
  197. package/method/validate.js +0 -157
@@ -1,197 +0,0 @@
1
- import genericPropSanitizer from './generic-prop-sanitizer.js'
2
-
3
- async function sanitizeFeature (item) {
4
- const { get, isPlainObject, mergeWith, isArray } = this.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.name
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.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.lib._
43
- const propTypes = keys(this.propType)
44
- const schemas = []
45
- this.log.debug('loadingDbSchemas')
46
- for (const k in items) {
47
- this.log.trace('- %s (%d)', k, keys(items[k]).length)
48
- for (const f in items[k]) {
49
- const item = items[k][f]
50
- item.cacheable = item.cacheable ?? true
51
- await runHook(`dobo.${camelCase(item.name)}:beforeSanitizeSchema`, item)
52
- const conn = find(this.connections, { name: item.connection })
53
- if (!conn) fatal.call(this, 'Unknown connection \'%s@%s\'', item.name, item.connection)
54
- item.fullText = item.fullText ?? { fields: [] }
55
- item.indexes = item.indexes ?? []
56
- let { ns, path: type } = breakNsPath(conn.type)
57
- if (isEmpty(type)) type = conn.type
58
- const driver = find(this.drivers, { type, ns, driver: conn.driver })
59
- if (driver.lowerCaseModel) item.name = item.name.toLowerCase()
60
- let file = `${ns}:/extend/${this.name}/lib/${type}/prop-sanitizer.js`
61
- let propSanitizer = await importModule(file)
62
- if (!propSanitizer) propSanitizer = genericPropSanitizer
63
- for (const idx in item.properties) {
64
- let prop = item.properties[idx]
65
- if (isString(prop)) {
66
- let [name, type, maxLength, index, required] = prop.split(':')
67
- if (!type) type = 'string'
68
- maxLength = maxLength ?? 255
69
- prop = { name, type }
70
- if (type === 'string') prop.maxLength = parseInt(maxLength) || undefined
71
- if (!isEmpty(index)) prop.index = { type: index === 'true' ? 'default' : index }
72
- prop.required = required === 'true'
73
- item.properties[idx] = prop
74
- } else {
75
- if (isString(prop.index)) prop.index = { type: prop.index }
76
- else if (prop.index === true) prop.index = { type: 'default' }
77
- }
78
- }
79
- const idx = findIndex(item.properties, { name: 'id' })
80
- if (idx === -1) item.properties.unshift(driver.idField)
81
- item.feature = item.feature ?? []
82
- await sanitizeFeature.call(this, item)
83
- item.disabled = item.disabled ?? []
84
- if (item.disabled === 'all') item.disabled = ['find', 'get', 'create', 'update', 'remove']
85
- else if (item.disabled === 'readonly') item.disabled = ['create', 'update', 'remove']
86
- for (const idx in item.properties) {
87
- let prop = item.properties[idx]
88
- if (!prop.type) {
89
- prop.type = 'string'
90
- prop.maxLength = 255
91
- }
92
- if (!propTypes.includes(prop.type)) {
93
- let success = false
94
- const feature = get(this.feature, isPlainObject(prop.type) ? prop.type.name : prop.type)
95
- if (feature) {
96
- let opts = { fieldName: prop.name }
97
- if (isPlainObject(prop.type)) opts = defaultsDeep(opts, prop.type)
98
- else opts.name = prop.type
99
- const feat = find(item.feature, opts)
100
- if (!feat) item.feature.push(opts)
101
- const input = await feature.call(this, opts)
102
- if (input.properties && input.properties.length > 0) {
103
- prop = input.properties[0]
104
- success = true
105
- }
106
- }
107
- if (!success) fatal.call(this, 'Unsupported property type \'%s@%s\' in \'%s\'', prop.type, prop.name, item.name)
108
- }
109
- if (prop.index) {
110
- if (prop.index === 'unique') prop.index = { type: 'unique' }
111
- else if (prop.index === 'fulltext') prop.index = { type: 'fulltext' }
112
- else if (prop.index === 'primary') prop.index = { type: 'primary' }
113
- else if (prop.index === true) prop.index = { type: 'default' }
114
- }
115
-
116
- await propSanitizer.call(this, { prop, schema: item, connection: conn, driver })
117
- if (prop.index && prop.index.type === 'primary' && prop.name !== 'id') fatal.call(this, 'Primary index should only be used for \'id\' field')
118
- if (prop.index && prop.index.type === 'fulltext') {
119
- item.fullText.fields.push(prop.name)
120
- prop.index.type = 'default'
121
- }
122
- item.properties[idx] = prop
123
- }
124
- await sanitizeIndexes.call(this, item)
125
- await sanitizeFullText.call(this, item)
126
- const all = []
127
- each(item.properties, p => {
128
- if (!all.includes(p.name)) all.push(p.name)
129
- else fatal.call(this, 'Field \'%s@%s\' should be used only once', p.name, item.name)
130
- })
131
- file = `${ns}:/extend/${this.name}/lib/${type}/schema-sanitizer.js`
132
- const schemaSanitizer = await importModule(file)
133
- if (schemaSanitizer) await schemaSanitizer.call(this, { schema: item, connection: conn, driver })
134
- schemas.push(item)
135
- }
136
- }
137
- for (const i in schemas) {
138
- const schema = schemas[i]
139
- const sortables = []
140
- const hidden = []
141
- for (const i2 in schema.properties) {
142
- const prop = schema.properties[i2]
143
- if (prop.type !== 'string') delete prop.maxLength
144
- if (prop.rel) {
145
- for (const key in prop.rel) {
146
- let def = prop.rel[key]
147
- if (isString(def)) {
148
- const [rschema, rfield] = def.split(':')
149
- def = { schema: rschema, propName: rfield }
150
- }
151
- def.type = def.type ?? 'one-to-one'
152
- const rel = find(schemas, { name: def.schema })
153
- if (!rel) {
154
- fatal.call(this, 'No schema found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
155
- }
156
- const rprop = find(rel.properties, { name: def.propName })
157
- if (!rprop) fatal.call(this, 'No property found for relationship \'%s@%s:%s\'', `${def.schema}:${def.propName}`, schema.name, prop.name)
158
- def.fields = def.fields ?? '*'
159
- if (['*', 'all'].includes(def.fields)) {
160
- const relschema = find(schemas, { name: def.schema })
161
- def.fields = relschema.properties.map(p => p.name)
162
- }
163
- if (def.fields.length > 0 && !def.fields.includes('id')) def.fields.push('id')
164
- for (const f of def.fields) {
165
- const p = find(rel.properties, { name: f })
166
- if (!p) fatal.call(this, 'Unknown property for field \'%s\' in relationship \'%s@%s:%s\'', p, `${def.schema}:${def.propName}`, schema.name, prop.name)
167
- }
168
- prop.type = rprop.type
169
- if (rprop.type === 'string') prop.maxLength = rprop.maxLength
170
- else {
171
- delete prop.maxLength
172
- delete prop.minLength
173
- }
174
- prop.rel[key] = def
175
- }
176
- // TODO: propSanitizer must be called again
177
- }
178
- if (prop.hidden) hidden.push(prop.name)
179
- if (prop.index) sortables.push(prop.name)
180
- delete prop.hidden
181
- schema.properties[i2] = prop
182
- }
183
- // sortables
184
- each(schema.indexes, item => {
185
- sortables.push(...item.fields)
186
- })
187
- // hidden
188
- schema.hidden = uniq(filter((schema.hidden ?? []).concat(hidden), item => map(schema.properties, 'name').includes(item)))
189
- schema.sortables = sortables
190
- await runHook(`dobo.${camelCase(schema.name)}:afterSanitizeSchema`, schema)
191
- }
192
- this.schemas = orderBy(schemas, ['buildLevel', 'name'])
193
- freeze(this.schemas)
194
- this.log.debug('loadedSchemas%s', join(map(this.schemas, 'name')))
195
- }
196
-
197
- export default sanitizeSchema
@@ -1,38 +0,0 @@
1
- async function singleRelRows ({ schema, record, options = {} }) {
2
- const { isSet } = this.lib.aneka
3
- const { find } = this.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.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.print.write('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.lib
5
- const { isEmpty } = this.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.print.write('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.lib
5
- const { getPluginDataDir } = this.app.bajo
6
- name = this.attachmentPreCheck(name)
7
- if (!name) return
8
- const dir = `${getPluginDataDir(this.name)}/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.lib.aneka
4
- const { fs } = this.lib
5
- const dir = `${getPluginDataDir(this.name)}/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
@@ -1,12 +0,0 @@
1
- async function get (name, id, field, file, options = {}) {
2
- name = this.attachmentPreCheck(name)
3
- if (!name) return
4
- const { find } = this.lib._
5
- const all = await this.attachmentFind(name, id, options)
6
- if (field === 'null') field = null
7
- const data = find(all, { field, file })
8
- if (!data) throw this.error('notFound', { statusCode: 404 })
9
- return data
10
- }
11
-
12
- export default get
@@ -1,9 +0,0 @@
1
- function preCheck (name) {
2
- const { pascalCase } = this.lib.aneka
3
- name = pascalCase(name)
4
- const schema = this.getSchema(name)
5
- if (!schema.attachment) return false
6
- return name
7
- }
8
-
9
- export default preCheck
@@ -1,11 +0,0 @@
1
- async function remove (name, id, field, file, options = {}) {
2
- const { fs } = this.lib
3
- name = this.attachmentPreCheck(name)
4
- if (!name) return
5
- const path = await this.attachmentGetPath(name, id, field, file)
6
- const { req } = options
7
- await fs.remove(path)
8
- if (req && req.flash) req.flash('notify', req.t('attachmentRemoved'))
9
- }
10
-
11
- export default remove
@@ -1,7 +0,0 @@
1
- import create from './create.js'
2
-
3
- async function update (name, id, options = {}) {
4
- return create.call(this, name, id, options)
5
- }
6
-
7
- export default update
@@ -1,46 +0,0 @@
1
- import buildBulkAction from '../../lib/build-bulk-action.js'
2
- import execValidation from '../../lib/exec-validation.js'
3
- import execFeatureHook from '../../lib/exec-feature-hook.js'
4
-
5
- async function create (name, inputs, options) {
6
- const { isSet } = this.lib.aneka
7
- const { generateId, runHook } = this.app.bajo
8
- const { clearModel } = this.cache ?? {}
9
- const { find } = this.lib._
10
- options.dataOnly = options.dataOnly ?? true
11
- options.truncateString = options.truncateString ?? true
12
- const { noHook, noValidation } = options
13
- await this.modelExists(name, true)
14
- const { handler, schema } = await buildBulkAction.call(this, name, 'create', options)
15
- const idField = find(schema.properties, { name: 'id' })
16
- const bodies = [...inputs]
17
- for (let b of bodies) {
18
- b.id = b.id ?? generateId(idField.type === 'integer' ? 'int' : undefined)
19
- b = await this.sanitizeBody({ body: b, schema, strict: true })
20
- if (!noValidation) b = await execValidation.call(this, { noHook, name, b, options })
21
- }
22
- if (!noHook) {
23
- await runHook(`${this.name}:beforeBulkCreate`, name, bodies, options)
24
- await runHook(`${this.name}.${name}:beforeBulkCreate`, bodies, options)
25
- }
26
- for (const idx in bodies) {
27
- await execFeatureHook.call(this, 'beforeCreate', { schema, body: bodies[idx] })
28
- // TODO: check unique?
29
- for (const k in bodies[idx]) {
30
- if (bodies[idx][k] === undefined) continue
31
- const prop = find(schema.properties, { name: k })
32
- if (options.truncateString && isSet(bodies[idx][k]) && prop && ['string', 'text'].includes(prop.type)) bodies[idx][k] = bodies[idx][k].slice(0, prop.maxLength)
33
- }
34
- }
35
- await handler.call(this, { schema, bodies, options })
36
- for (const idx in bodies) {
37
- await execFeatureHook.call(this, 'afterCreate', { schema, body: bodies[idx] })
38
- }
39
- if (!noHook) {
40
- await runHook(`${this.name}.${name}:afterBulkCreate`, bodies, options)
41
- await runHook(`${this.name}:afterBulkCreate`, name, bodies, options)
42
- }
43
- if (clearModel) await clearModel({ model: name })
44
- }
45
-
46
- export default create
@@ -1,22 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- async function clear (name, options = {}) {
4
- const { runHook } = this.app.bajo
5
- const { camelCase } = this.lib._
6
-
7
- await this.modelExists(name, true)
8
- const { noHook } = options
9
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-clear', options)
10
- if (!noHook) {
11
- await runHook(`${this.name}:beforeModelClear`, schema, options)
12
- await runHook(`${this.name}.${camelCase(name)}:beforeModelClear`, options)
13
- }
14
- const resp = await handler.call(this.app[driver.ns], { schema, options })
15
- if (!noHook) {
16
- await runHook(`${this.name}.${camelCase(name)}:afterModelClear`, options, resp)
17
- await runHook(`${this.name}:afterModelClear`, schema, options, resp)
18
- }
19
- return resp
20
- }
21
-
22
- export default clear
@@ -1,19 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- async function create (name, options = {}) {
4
- const { runHook } = this.app.bajo
5
- const { camelCase } = this.lib._
6
-
7
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-create', options)
8
- if (!options.noHook) {
9
- await runHook(`${this.name}:beforeModelCreate`, schema, options)
10
- await runHook(`${this.name}.${camelCase(name)}:beforeModelCreate`, options)
11
- }
12
- await handler.call(this.app[driver.ns], { schema, options })
13
- if (!options.noHook) {
14
- await runHook(`${this.name}.${camelCase(name)}:afterModelCreate`, options)
15
- await runHook(`${this.name}:afterModelCreate`, schema, options)
16
- }
17
- }
18
-
19
- export default create
@@ -1,19 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- async function drop (name, options = {}) {
4
- const { runHook } = this.app.bajo
5
- const { camelCase } = this.lib._
6
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-drop', options)
7
-
8
- if (!options.noHook) {
9
- await runHook(`${this.name}:beforeModelDrop`, schema, options)
10
- await runHook(`${this.name}.${camelCase(name)}:beforeModelDrop`, options)
11
- }
12
- await handler.call(this.app[driver.ns], { schema, options })
13
- if (!options.noHook) {
14
- await runHook(`${this.name}.${camelCase(name)}:afterModelDrop`, options)
15
- await runHook(`${this.name}:afterModelDrop`, schema, options)
16
- }
17
- }
18
-
19
- export default drop
@@ -1,24 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- const cache = {}
4
-
5
- async function exists (name, thrown, options = {}) {
6
- if (cache[name]) return cache[name]
7
- const { runHook } = this.app.bajo
8
- const { camelCase } = this.lib._
9
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-exists', options)
10
- if (!options.noHook) {
11
- await runHook(`${this.name}:beforeModelExists`, schema, options)
12
- await runHook(`${this.name}.${camelCase(name)}:beforeModelExists`, options)
13
- }
14
- const exist = await handler.call(this.app[driver.ns], { schema, options })
15
- if (!options.noHook) {
16
- await runHook(`${this.name}.${camelCase(name)}:afterModelExists`, exist, options)
17
- await runHook(`${this.name}:afterModelExists`, schema, exist, options)
18
- }
19
- if (!exist && thrown) throw this.error('modelNotExists%s', name)
20
- cache[name] = exist
21
- return exist
22
- }
23
-
24
- export default exists
@@ -1,24 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
-
3
- async function clear (name, opts = {}) {
4
- const { runHook } = this.app.bajo
5
- await this.modelExists(name, true)
6
- const { cloneDeep, camelCase, omit } = this.lib._
7
- const options = cloneDeep(omit(opts, ['req', 'reply']))
8
- options.req = opts.req
9
- options.reply = opts.reply
10
- const { noHook } = options
11
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-clear', options)
12
- if (!noHook) {
13
- await runHook(`${this.name}:beforeRecordClear`, name, options)
14
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordClear`, options)
15
- }
16
- const resp = await handler.call(this.app[driver.ns], { schema, options })
17
- if (!noHook) {
18
- await runHook(`${this.name}.${camelCase(name)}:afterRecordClear`, options, resp)
19
- await runHook(`${this.name}:afterRecordClear`, name, options, resp)
20
- }
21
- return resp
22
- }
23
-
24
- export default clear
@@ -1,44 +0,0 @@
1
- import resolveMethod from '../../lib/resolve-method.js'
2
- import execFeatureHook from '../../lib/exec-feature-hook.js'
3
-
4
- async function count (name, filter = {}, opts = {}) {
5
- const { runHook } = this.app.bajo
6
- const { get, set } = this.cache ?? {}
7
- const { cloneDeep, camelCase, omit } = this.lib._
8
- delete opts.record
9
- const options = cloneDeep(omit(opts, ['req', 'reply']))
10
- options.req = opts.req
11
- options.reply = opts.reply
12
- options.dataOnly = options.dataOnly ?? true
13
- let { dataOnly, noHook, noCache, noFeatureHook } = options
14
- options.dataOnly = false
15
- await this.modelExists(name, true)
16
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-count', options)
17
- if (!schema.cacheable) noCache = true
18
- filter.query = this.buildQuery({ filter, schema, options }) ?? {}
19
- if (options.queryHandler) filter.query = await options.queryHandler.call(opts.req ? this.app[opts.req.ns] : this, filter.query, opts.req)
20
- filter.match = this.buildMatch({ input: filter.match, schema, options }) ?? {}
21
- if (!noHook) {
22
- await runHook(`${this.name}:beforeRecordCount`, name, filter, options)
23
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordCount`, filter, options)
24
- }
25
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCount', { schema, filter, options })
26
- if (get && !noCache && !options.record) {
27
- const cachedResult = await get({ model: name, filter, options })
28
- if (cachedResult) {
29
- cachedResult.cached = true
30
- return dataOnly ? cachedResult.data : cachedResult
31
- }
32
- }
33
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
34
- delete options.record
35
- if (!noHook) {
36
- await runHook(`${this.name}.${camelCase(name)}:afterRecordCount`, filter, options, record)
37
- await runHook(`${this.name}:afterRecordCount`, name, filter, options, record)
38
- }
39
- if (set && !noCache) await set({ model: name, filter, options, record })
40
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterCount', { schema, filter, options, record })
41
- return dataOnly ? record.data : record
42
- }
43
-
44
- export default count
@@ -1,71 +0,0 @@
1
- import crypto from 'crypto'
2
- import resolveMethod from '../../lib/resolve-method.js'
3
- import checkUnique from '../../lib/check-unique.js'
4
- import handleAttachmentUpload from '../../lib/handle-attachment-upload.js'
5
- import execValidation from '../../lib/exec-validation.js'
6
- import execFeatureHook from '../../lib/exec-feature-hook.js'
7
- import singleRelRows from '../../lib/single-rel-rows.js'
8
-
9
- async function create (name, input, opts = {}) {
10
- const { generateId, runHook } = this.app.bajo
11
- const { isSet } = this.lib.aneka
12
- const { clearModel } = this.cache ?? {}
13
- const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.lib._
14
- delete opts.record
15
- const options = cloneDeep(omit(opts, ['req', 'reply']))
16
- options.req = opts.req
17
- options.reply = opts.reply
18
- options.dataOnly = options.dataOnly ?? true
19
- input = cloneDeep(input)
20
- const { fields, dataOnly, noHook, noValidation, noCheckUnique, noFeatureHook, noResult, noSanitize, hidden, forceNoHidden } = options
21
- options.truncateString = options.truncateString ?? true
22
- options.dataOnly = false
23
- await this.modelExists(name, true)
24
- const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-create', options)
25
- const idField = find(schema.properties, { name: 'id' })
26
- const extFields = get(options, 'validation.extFields', [])
27
- let body = noSanitize ? cloneDeep(input) : await this.sanitizeBody({ body: input, schema, extFields, strict: true })
28
- if (!noHook) {
29
- await runHook(`${this.name}:beforeRecordCreate`, name, body, options)
30
- await runHook(`${this.name}.${camelCase(name)}:beforeRecordCreate`, body, options)
31
- }
32
- if (!isSet(body.id)) {
33
- if (idField.type === 'string') {
34
- if (!options.checksumId) body.id = generateId()
35
- else {
36
- if (options.checksumId === true) options.checksumId = Object.keys(body)
37
- const checksum = pick(body, options.checksumId)
38
- body.id = crypto.createHash('md5').update(JSON.stringify(checksum)).digest('hex')
39
- }
40
- } else if (['integer', 'smallint'].includes(idField.type) && !idField.autoInc) input.id = generateId('int')
41
- }
42
- if (!noValidation) body = await execValidation.call(this, { name, body, options })
43
- if (isSet(body.id) && !noCheckUnique) await checkUnique.call(this, { schema, body })
44
- const nbody = {}
45
- forOwn(body, (v, k) => {
46
- if (v === undefined) return undefined
47
- const prop = find(schema.properties, { name: k })
48
- if (!prop) return undefined
49
- if (options.truncateString && isSet(v) && ['string', 'text'].includes(prop.type)) v = v.slice(0, prop.maxLength)
50
- nbody[k] = v
51
- })
52
- if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body: nbody, options })
53
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, body: nbody, options }))
54
- delete options.record
55
- if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
56
- if (options.req) {
57
- if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id: body.id, body, options, action: 'create' })
58
- if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordCreated'))
59
- }
60
- if (clearModel) await clearModel({ model: name, body: nbody, options, record })
61
- if (noResult) return
62
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
63
- if (!noHook) {
64
- await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
65
- await runHook(`${this.name}:afterRecordCreate`, name, nbody, options, record)
66
- }
67
- if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body: nbody, options, record })
68
- return dataOnly ? record.data : record
69
- }
70
-
71
- export default create