dobo 1.0.1 → 1.0.3

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 (72) hide show
  1. package/README.md +1 -1
  2. package/bajo/.alias +1 -0
  3. package/bajo/config.json +12 -2
  4. package/bajo/init.js +14 -3
  5. package/bajo/method/attachment/create.js +1 -1
  6. package/bajo/method/attachment/get-path.js +1 -1
  7. package/bajo/method/attachment/get.js +1 -1
  8. package/bajo/method/attachment/remove.js +1 -1
  9. package/bajo/method/build-query.js +1 -0
  10. package/bajo/method/bulk/create.js +6 -6
  11. package/bajo/method/get-connection.js +6 -0
  12. package/bajo/method/get-info.js +2 -2
  13. package/bajo/method/get-schema.js +2 -2
  14. package/bajo/method/model/clear.js +7 -5
  15. package/bajo/method/model/create.js +10 -2
  16. package/bajo/method/model/drop.js +10 -2
  17. package/bajo/method/model/exists.js +9 -2
  18. package/bajo/method/pick-record.js +14 -8
  19. package/bajo/method/prep-pagination.js +5 -9
  20. package/bajo/method/record/clear.js +8 -7
  21. package/bajo/method/record/create.js +26 -28
  22. package/bajo/method/record/find-one.js +10 -9
  23. package/bajo/method/record/find.js +11 -9
  24. package/bajo/method/record/get.js +10 -9
  25. package/bajo/method/record/remove.js +13 -12
  26. package/bajo/method/record/update.js +22 -25
  27. package/bajo/method/record/upsert.js +4 -3
  28. package/bajo/method/sanitize/body.js +8 -9
  29. package/bajo/method/stat/aggregate.js +6 -6
  30. package/bajo/method/stat/histogram.js +5 -5
  31. package/bajo/method/validate.js +23 -20
  32. package/bajo/start.js +6 -2
  33. package/bajoCli/applet/connection.js +1 -1
  34. package/bajoCli/applet/lib/post-process.js +13 -13
  35. package/bajoCli/applet/model-clear.js +2 -2
  36. package/bajoCli/applet/model-rebuild.js +12 -9
  37. package/bajoCli/applet/record-create.js +2 -2
  38. package/bajoCli/applet/record-find.js +2 -2
  39. package/bajoCli/applet/record-get.js +2 -2
  40. package/bajoCli/applet/record-remove.js +2 -2
  41. package/bajoCli/applet/record-update.js +2 -2
  42. package/bajoCli/applet/schema.js +1 -1
  43. package/bajoCli/applet/stat-count.js +2 -2
  44. package/bajoI18N/resource/en-US.json +28 -27
  45. package/bajoI18N/resource/id.json +60 -27
  46. package/lib/add-fixtures.js +4 -4
  47. package/lib/check-unique.js +2 -2
  48. package/lib/collect-connections.js +3 -4
  49. package/lib/collect-drivers.js +11 -3
  50. package/lib/collect-feature.js +6 -5
  51. package/lib/collect-schemas.js +9 -7
  52. package/lib/exec-validation.js +8 -14
  53. package/lib/generic-prop-sanitizer.js +1 -1
  54. package/lib/mem-db/conn-sanitizer.js +8 -0
  55. package/lib/mem-db/instantiate.js +41 -0
  56. package/lib/mem-db/method/model/clear.js +6 -0
  57. package/lib/mem-db/method/model/create.js +5 -0
  58. package/lib/mem-db/method/model/drop.js +5 -0
  59. package/lib/mem-db/method/model/exists.js +5 -0
  60. package/lib/mem-db/method/record/create.js +12 -0
  61. package/lib/mem-db/method/record/find.js +20 -0
  62. package/lib/mem-db/method/record/get.js +9 -0
  63. package/lib/mem-db/method/record/remove.js +13 -0
  64. package/lib/mem-db/method/record/update.js +15 -0
  65. package/lib/mem-db/method/stat/count.js +11 -0
  66. package/lib/mem-db/start.js +25 -0
  67. package/lib/resolve-method.js +4 -3
  68. package/lib/sanitize-schema.js +21 -9
  69. package/package.json +5 -3
  70. package/bajo/hook/bajoI18N@before-init.js +0 -6
  71. package/bajoCli/applet/shell.js +0 -48
  72. /package/bajo/hook/{bajoI18N.db@before-resource-merge.js → bajo-i18n.db@before-resource-merge.js} +0 -0
@@ -49,34 +49,35 @@
49
49
  "siteId": "Site ID",
50
50
  "theme": "Theme",
51
51
  "token": "Token",
52
- "field": "Field"
52
+ "field": "Field",
53
+ "agree": "Agreement"
53
54
  },
54
55
  "validation": {
55
- "any.required": "Harus diisi",
56
- "any.only": "Harus sesuai dengan {{ref}}",
57
- "string.alphanum": "Harus berupa alfa numerik karakter saja",
58
- "string.base": "Harus berupa string",
59
- "string.base64": "Harus berupa string base64 yang valid",
60
- "string.creditCard": "Harus berupa nomor kartu kredit",
61
- "string.dataUri": "Harus berupa string dataUri yang valid",
62
- "string.domain": "Harus mengandung nama domain yang valid",
63
- "string.email": "Harus berupa surel yang valid",
64
- "string.empty": "Tidak boleh dibiarkan kosong",
65
- "string.guid": "Harus berupa GUID yang valid",
66
- "string.hex": "Harus mengandung hexadesimal karakter yang valid saja",
67
- "string.hexAlign": "Representasi hex yang terdekode harus byte aligned",
68
- "string.hostname": "Harus berupa nama host yang valid",
69
- "string.ip": "Harus berupa alamat ip yang valid dengan CIDR {{cidr}}",
70
- "string.ipVersion": "Harus berupa alamat ip yang valid dengan versi {{version}} dan CIDR {{cidr}}",
71
- "string.isoDate": "Harus dalam format ISO",
72
- "string.isoDuration": "Harus berupa durasi ISO 8601 yang valid",
73
- "string.length": "Panjang harus pas {{limit}} karakter",
74
- "string.lowercase": "Hanya boleh mengandung huruf kecil saja",
75
- "string.max": "Panjang karakter harus lebih kecil atau sama dengan {{limit}} karakter",
76
- "string.min": "Panjang karakter harus setidaknya {{limit}} karakter",
77
- "string.token": "Hanya boleh terdiri atas karakter alfanumerik dan garis bawah saja",
78
- "string.trim": "Tidak boleh memiliki spasi didepan atau dibelakang",
79
- "string.uri": "Harus berupa URI yang valid",
80
- "string.uppercase": "Hanya boleh mengandung huruf besar saja"
56
+ "any.required": "Required",
57
+ "any.only": "Must match with {{ref}}",
58
+ "string.alphanum": "Must only contain alpha-numeric characters",
59
+ "string.base": "Must be a string",
60
+ "string.base64": "Must be a valid base64 string",
61
+ "string.creditCard": "Must be a credit card",
62
+ "string.dataUri": "Must be a valid dataUri string",
63
+ "string.domain": "Must contain a valid domain name",
64
+ "string.email": "Must be a valid email",
65
+ "string.empty": "Required",
66
+ "string.guid": "Must be a valid GUID",
67
+ "string.hex": "Must only contain hexadecimal characters",
68
+ "string.hexAlign": "Hex decoded representation must be byte aligned",
69
+ "string.hostname": "Must be a valid hostname",
70
+ "string.ip": "must be a valid ip address with a {{cidr}} CIDR",
71
+ "string.ipVersion": "Must be a valid ip address of one of the following versions {{version}} with a {{cidr}} CIDR",
72
+ "string.isoDate": "Must be in iso format",
73
+ "string.isoDuration": "Must be a valid ISO 8601 duration",
74
+ "string.length": "Length must be {{limit}} characters long",
75
+ "string.lowercase": "Must only contain lowercase characters",
76
+ "string.max": "Length must be less than or equal to {{limit}} characters long",
77
+ "string.min": "Length must be at least {{limit}} characters long",
78
+ "string.token": "Must only contain alpha-numeric and underscore characters",
79
+ "string.trim": "Must not have leading or trailing whitespace",
80
+ "string.uri": "Must be a valid uri",
81
+ "string.uppercase": "Must only contain uppercase characters"
81
82
  }
82
83
  }
@@ -59,6 +59,19 @@
59
59
  "Unsupported aggregate '%s'": "Unsupported aggregate '%s'",
60
60
  "Unsupported %s '%s' for '%s@%s'. Allowed choices: %s": "Unsupported %s '%s' for '%s@%s'. Allowed choices: %s",
61
61
  "Method '%s@%s' is disabled": "Metode '%s@%s' dinonaktifkan",
62
+ "Invalid model for persistence: %s": "Model untuk penyimpanan tidak valid: %s",
63
+ "Can't load %s: %s": "Tidak bisa memuat %s: %s",
64
+ "No record found": "Tidak ditemukan data",
65
+ "%d record(s) found": "Ditemukan %d data",
66
+ "Add": "Tambah",
67
+ "Edit": "Ubah",
68
+ "Delete": "Hapus",
69
+ "Export": "Ekspor",
70
+ "Query": "Kueri",
71
+ "Query Builder": "Pembuat Kueri",
72
+ "List": "Daftar",
73
+ "Submit Query": "Kirim Kueri",
74
+ "Data Export": "Ekspor Data",
62
75
  "field": {
63
76
  "id": "ID",
64
77
  "name": "Nama",
@@ -109,35 +122,55 @@
109
122
  "siteId": "ID Situs",
110
123
  "theme": "Tema",
111
124
  "token": "Token",
112
- "field": "Field"
125
+ "field": "Field",
126
+ "agree": "Persetujuan"
113
127
  },
114
128
  "Validation Error": "Kesalahan Validasi",
115
129
  "validation": {
116
- "any.required": "Harus diisi",
117
- "any.only": "Harus sesuai dengan {{ref}}",
118
- "string.alphanum": "Harus berupa alfa numerik karakter saja",
119
- "string.base": "Harus berupa string",
120
- "string.base64": "Harus berupa string base64 yang valid",
121
- "string.creditCard": "Harus berupa nomor kartu kredit",
122
- "string.dataUri": "Harus berupa string dataUri yang valid",
123
- "string.domain": "Harus mengandung nama domain yang valid",
124
- "string.email": "Harus berupa surel yang valid",
125
- "string.empty": "Tidak boleh dibiarkan kosong",
126
- "string.guid": "Harus berupa GUID yang valid",
127
- "string.hex": "Harus mengandung hexadesimal karakter yang valid saja",
128
- "string.hexAlign": "Representasi hex yang terdekode harus byte aligned",
129
- "string.hostname": "Harus berupa nama host yang valid",
130
- "string.ip": "Harus berupa alamat ip yang valid dengan CIDR {{cidr}}",
131
- "string.ipVersion": "Harus berupa alamat ip yang valid dengan versi {{version}} dan CIDR {{cidr}}",
132
- "string.isoDate": "Harus dalam format ISO",
133
- "string.isoDuration": "Harus berupa durasi ISO 8601 yang valid",
134
- "string.length": "Panjang harus pas {{limit}} karakter",
135
- "string.lowercase": "Hanya boleh mengandung huruf kecil saja",
136
- "string.max": "Panjang karakter harus lebih kecil atau sama dengan {{limit}} karakter",
137
- "string.min": "Panjang karakter harus setidaknya {{limit}} karakter",
138
- "string.token": "Hanya boleh terdiri atas karakter alfanumerik dan garis bawah saja",
139
- "string.trim": "Tidak boleh memiliki spasi didepan atau dibelakang",
140
- "string.uri": "Harus berupa URI yang valid",
141
- "string.uppercase": "Hanya boleh mengandung huruf besar saja"
130
+ "any": {
131
+ "required": "Harus diisi/dipilih",
132
+ "only": "Harus sesuai dengan {{ref}}"
133
+ },
134
+ "string": {
135
+ "alphanum": "Harus berupa alfa numerik karakter saja",
136
+ "base": "Harus berupa string",
137
+ "base64": "Harus berupa string base64 yang valid",
138
+ "creditCard": "Harus berupa nomor kartu kredit",
139
+ "dataUri": "Harus berupa string dataUri yang valid",
140
+ "domain": "Harus mengandung nama domain yang valid",
141
+ "email": "Harus berupa surel yang valid",
142
+ "empty": "Harus diisi/dipilih",
143
+ "guid": "Harus berupa GUID yang valid",
144
+ "hex": "Harus mengandung hexadesimal karakter yang valid saja",
145
+ "hexAlign": "Representasi hex yang terdekode harus byte aligned",
146
+ "hostname": "Harus berupa nama host yang valid",
147
+ "ip": "Harus berupa alamat ip yang valid dengan CIDR {{cidr}}",
148
+ "ipVersion": "Harus berupa alamat ip yang valid dengan versi {{version}} dan CIDR {{cidr}}",
149
+ "isoDate": "Harus dalam format ISO",
150
+ "isoDuration": "Harus berupa durasi ISO 8601 yang valid",
151
+ "length": "Panjang harus pas {{limit}} karakter",
152
+ "lowercase": "Hanya boleh mengandung huruf kecil saja",
153
+ "max": "Panjang karakter harus lebih kecil atau sama dengan {{limit}} karakter",
154
+ "min": "Panjang karakter harus setidaknya {{limit}} karakter",
155
+ "token": "Hanya boleh terdiri atas karakter alfanumerik dan garis bawah saja",
156
+ "trim": "Tidak boleh memiliki spasi didepan atau dibelakang",
157
+ "uri": "Harus berupa URI yang valid",
158
+ "uppercase": "Hanya boleh mengandung huruf besar saja"
159
+ },
160
+ "number": {
161
+ "base": "Harus berupa angka",
162
+ "greater": "Harus lebih besar dari {{limit}}",
163
+ "infinity": "Tidak bisa tak terbatas",
164
+ "integer": "Harus berupa integer",
165
+ "less": "Harus lebih kecil dari {{limit}}",
166
+ "max": "Harus lebih kecil atau sama dengan {{limit}}",
167
+ "min": "Harus lebih besar atau sama dengan {{limit}}",
168
+ "multiple": "Harus kelipatan dari {{multiple}}",
169
+ "negative": "Harus berupa angka negatif",
170
+ "port": "Harus berupa nomor port yang valid",
171
+ "positive": "Harus berupa angka positif",
172
+ "precision": "Harus memiliki tidak lebih dari {{limit}} tempat desimal",
173
+ "unsafe": "Harus berupa nomor yang aman"
174
+ }
142
175
  }
143
176
  }
@@ -1,6 +1,6 @@
1
1
  import path from 'path'
2
2
 
3
- async function addFixture (name, spin) {
3
+ async function addFixture (name, { spinner } = {}) {
4
4
  const { resolvePath, readConfig, eachPlugins, getPluginDataDir } = this.app.bajo
5
5
  const { isEmpty, isArray } = this.app.bajo.lib._
6
6
  const { schema, connection } = this.getInfo(name)
@@ -15,12 +15,12 @@ async function addFixture (name, spin) {
15
15
  let items = await readConfig(pattern, { ns: schema.ns, ignoreError: true })
16
16
  if (isEmpty(items)) items = []
17
17
  // override
18
- const overrides = await readConfig(`${getPluginDataDir(this.name)}/fixture/override/${schema.name}.*`, { ns: this.name, ignoreError: true })
18
+ const overrides = await readConfig(`${getPluginDataDir(this.name)}/override/fixture/${schema.name}.*`, { ns: this.name, ignoreError: true })
19
19
  if (isArray(overrides) && !isEmpty(overrides)) items = overrides
20
20
  // extend
21
21
  const me = this
22
22
  await eachPlugins(async function ({ dir, ns }) {
23
- const extend = await readConfig(`${dir}/${me.name}/fixture/extend/${schema.name}.*`, { ns, ignoreError: true })
23
+ const extend = await readConfig(`${dir}/${me.name}/extend/fixture/${schema.name}.*`, { ns, ignoreError: true })
24
24
  if (isArray(extend) && !isEmpty(extend)) items.push(...extend)
25
25
  })
26
26
  if (isEmpty(items)) return result
@@ -39,7 +39,7 @@ async function addFixture (name, spin) {
39
39
  }
40
40
  await this.recordCreate(schema.name, item, { force: true })
41
41
  result.success++
42
- if (spin) spin.setText('%s: %d of %d records added', schema.name, result.success, items.length)
42
+ if (spinner) spinner.setText('%s: %d of %d records added', schema.name, result.success, items.length)
43
43
  } catch (err) {
44
44
  err.model = schema.name
45
45
  if (this.app.bajo.applet) this.print.fail(this.validationErrorMessage(err))
@@ -2,7 +2,7 @@ async function checkUnique ({ schema, body, id }) {
2
2
  const { isSet } = this.app.bajo
3
3
  const { filter, map, set } = this.app.bajo.lib._
4
4
  const singles = map(filter(schema.properties, p => (p.index ?? {}).type === 'unique'), 'name')
5
- const opts = { noHook: true, noCache: true, thrownNotFound: false }
5
+ const opts = { noHook: true, noCache: true, thrownNotFound: false, force: true }
6
6
  let old = {}
7
7
  if (id) old = (await this.recordGet(schema.name, id, opts)) ?? {}
8
8
  for (const s of singles) {
@@ -26,7 +26,7 @@ async function checkUnique ({ schema, body, id }) {
26
26
  query[f] = body[f]
27
27
  }
28
28
  if (empty || same) continue
29
- const resp = await this.recordFind(schema.name, { query, limit: 1 }, { noHook: true, noCache: true })
29
+ const resp = await this.recordFind(schema.name, { query, limit: 1 }, { noHook: true, noCache: true, force: true })
30
30
  if (resp.length !== 0) {
31
31
  const details = map(m.fields, f => {
32
32
  return { field: f, error: 'Unique constraint error' }
@@ -1,7 +1,5 @@
1
1
  async function defSanitizer (item) {
2
- // if (!item.connection) fatal('\'%s@%s\' key is required', 'connection', item.name, { payload: item })
3
- const { merge } = this.app.bajo.lib._
4
- return merge({}, item)
2
+ return item
5
3
  }
6
4
 
7
5
  async function collectConnections ({ item, index, options }) {
@@ -9,10 +7,11 @@ async function collectConnections ({ item, index, options }) {
9
7
  const { importModule, breakNsPath } = this.app.bajo
10
8
  const { has, find } = this.app.bajo.lib._
11
9
  if (!has(conn, 'type')) this.fatal('Connection must have a valid DB type')
12
- const [ns, type] = breakNsPath(conn.type)
10
+ const { ns, path: type } = breakNsPath(conn.type)
13
11
  const driver = find(this.drivers, { ns, type })
14
12
  if (!driver) this.fatal('Unsupported DB type \'%s\'', conn.type)
15
13
  let file = `${ns}:/${this.name}/lib/${type}/conn-sanitizer.js`
14
+ if (conn.type === 'dobo:memory') file = `${ns}:/lib/mem-db/conn-sanitizer.js`
16
15
  if (driver.provider) file = `${driver.provider}:/${ns}/lib/${type}/conn-sanitizer.js`
17
16
  let sanitizer = await importModule(file)
18
17
  if (!sanitizer) sanitizer = defSanitizer
@@ -1,15 +1,23 @@
1
1
  async function collectDrivers () {
2
2
  const { eachPlugins, readConfig, runHook } = this.app.bajo
3
- const { isString, find, pick, merge } = this.app.bajo.lib._
3
+ const { isString, find, pick, merge, cloneDeep } = this.app.bajo.lib._
4
4
  const me = this
5
5
  me.drivers = []
6
+ // built-in memory driver
7
+ me.drivers.push({
8
+ type: 'memory',
9
+ ns: me.name,
10
+ driver: 'memory',
11
+ idField: merge(cloneDeep(me.config.default.idField), { name: 'id' })
12
+ })
13
+ // others
6
14
  await runHook(`${this.name}:beforeCollectDrivers`)
7
15
  await eachPlugins(async function ({ file, ns }) {
8
16
  const info = await readConfig(file, { ns })
9
17
  if (!info.type) this.fatal('A DB driver must provide at least one database type')
10
18
  if (!info.driver) this.fatal('A DB driver must have a driver name')
11
19
  if (isString(info.type)) info.type = [info.type]
12
- if (!info.idField) info.idField = me.config.defaults.idField
20
+ if (!info.idField) info.idField = cloneDeep(me.config.default.idField)
13
21
  info.idField.name = 'id'
14
22
  for (const t of info.type) {
15
23
  const [type, provider] = t.split('@')
@@ -25,7 +33,7 @@ async function collectDrivers () {
25
33
  }
26
34
  me.drivers.push(merge(ext, driver))
27
35
  }
28
- }, { glob: 'boot/driver.*', baseNs: this.name })
36
+ }, { glob: 'boot/driver.*', prefix: this.name })
29
37
  await runHook(`${this.name}:afterCollectDrivers`)
30
38
  }
31
39
 
@@ -3,20 +3,21 @@ import path from 'path'
3
3
  async function handler ({ file, alias, ns }) {
4
4
  const { importModule } = this.app.bajo
5
5
  const { camelCase, isFunction } = this.app.bajo.lib._
6
+ const me = this.app.dobo
7
+
6
8
  let name = camelCase(path.basename(file, '.js'))
7
- if (ns !== this.name) name = `${ns}:${name}`
9
+ if (ns !== me.name) name = `${ns}.${name}`
8
10
  const mod = await importModule(file)
9
11
  if (!isFunction(mod)) this.fatal('Feature \'%s\' should be an async function', name)
10
- this.feature = this.feature ?? {}
11
- this.feature[name] = mod
12
- this.log.trace('- %s', name)
12
+ me.feature[name] = mod
13
+ me.log.trace('- %s', name)
13
14
  }
14
15
 
15
16
  async function collectFeature () {
16
17
  const { eachPlugins } = this.app.bajo
17
18
  this.feature = {}
18
19
  this.log.trace('Loading DB feature')
19
- await eachPlugins(handler, { glob: 'feature/*.js', baseNs: this.ns })
20
+ await eachPlugins(handler, { glob: 'feature/*.js', prefix: this.name })
20
21
  this.log.debug('Total loaded features: %d', Object.keys(this.feature).length)
21
22
  }
22
23
 
@@ -3,15 +3,17 @@ import sanitizeSchema from './sanitize-schema.js'
3
3
 
4
4
  async function handler ({ file, alias, ns }) {
5
5
  const { readConfig, pascalCase, eachPlugins } = this.app.bajo
6
- const { isPlainObject, each, find, has, isArray, forOwn, isString, merge } = this.app.bajo.lib._
6
+ const { get, isPlainObject, each, find, has, isArray, forOwn, isString, merge } = this.app.bajo.lib._
7
7
  const { fastGlob } = this.app.bajo.lib
8
8
 
9
- const defName = pascalCase(`${alias} ${path.basename(file, path.extname(file))}`)
9
+ const base = path.basename(file, path.extname(file))
10
+ const defName = pascalCase(`${alias} ${base}`)
10
11
  const mod = await readConfig(file, { ns, ignoreError: true })
11
12
  if (!isPlainObject(mod)) this.fatal('Invalid schema \'%s\'', defName)
12
- mod.name = mod.name ?? defName
13
- mod.modelName = mod.modelName ?? mod.name
13
+ const forcedConn = get(this, `app.${ns}.config.dobo.schemaConnection.${base}`)
14
+ if (forcedConn) mod.connection = forcedConn
14
15
  if (!mod.connection) mod.connection = 'default'
16
+ mod.name = mod.name ?? defName
15
17
  mod.file = file
16
18
  mod.ns = ns
17
19
  mod.attachment = mod.attachment ?? true
@@ -32,7 +34,7 @@ async function handler ({ file, alias, ns }) {
32
34
  // schema extender
33
35
  const me = this
34
36
  await eachPlugins(async function (opts) {
35
- const glob = `${opts.dir}/${me.name}/schema/extend/${mod.name}.*`
37
+ const glob = `${opts.dir}/${me.name}/extend/schema/${mod.name}.*`
36
38
  const files = await fastGlob(glob)
37
39
  for (const file of files) {
38
40
  const extender = await readConfig(file, { ns: opts.ns, ignoreError: true })
@@ -55,7 +57,7 @@ async function handler ({ file, alias, ns }) {
55
57
  }
56
58
  if (feats.length > 0) mod.feature.push(...feats)
57
59
  if (opts.plugin === this.app.bajo.mainNs) {
58
- each(['connection', 'modelName'], i => {
60
+ each(['connection', 'name'], i => {
59
61
  if (has(extender, i)) mod[i] = extender[i]
60
62
  })
61
63
  }
@@ -69,7 +71,7 @@ async function handler ({ file, alias, ns }) {
69
71
  async function collectSchemas () {
70
72
  const { eachPlugins } = this.app.bajo
71
73
  const { isEmpty } = this.app.bajo.lib._
72
- const result = await eachPlugins(handler, { glob: 'schema/*.*', baseNs: this.name })
74
+ const result = await eachPlugins(handler, { glob: 'schema/*.*', prefix: this.name })
73
75
  if (isEmpty(result)) this.log.warn('No %s found!', this.print.write('schema'))
74
76
  else await sanitizeSchema.call(this, result)
75
77
  }
@@ -1,25 +1,19 @@
1
- async function execValidation ({ noHook, name, body, options, partial }) {
1
+ async function execValidation ({ name, body, options, partial }) {
2
2
  const { runHook } = this.app.bajo
3
- const { get, keys } = this.app.bajo.lib._
3
+ const { keys, camelCase } = this.app.bajo.lib._
4
+ const { noHook } = options
4
5
  if (!noHook) {
5
- await runHook(`${this.name}:onBeforeRecordValidation`, name, body, options)
6
- await runHook(`${this.name}.${name}:onBeforeRecordValidation`, body, options)
6
+ await runHook(`${this.name}:beforeRecordValidation`, name, body, options)
7
+ await runHook(`${this.name}.${camelCase(name)}:beforeRecordValidation`, body, options)
7
8
  }
8
9
  const { validation = {} } = options
9
10
  if (partial) {
10
11
  validation.fields = keys(body)
11
12
  }
12
- try {
13
- body = await this.validate(body, name, validation)
14
- } catch (err) {
15
- if (err.code === 'DB_VALIDATION' && get(options, 'req.flash')) {
16
- options.req.flash('validation', err)
17
- }
18
- throw err
19
- }
13
+ body = await this.validate(body, name, validation)
20
14
  if (!noHook) {
21
- await runHook(`${this.name}:onAfterRecordValidation`, name, body, options)
22
- await runHook(`${this.name}.${name}:onAfterRecordValidation`, body, options)
15
+ await runHook(`${this.name}:afterRecordValidation`, name, body, options)
16
+ await runHook(`${this.name}.${camelCase(name)}:afterRecordValidation`, body, options)
23
17
  }
24
18
  return body
25
19
  }
@@ -23,7 +23,7 @@ async function genericPropSanitizer ({ prop, schema, driver }) {
23
23
  delete prop[p]
24
24
  return undefined
25
25
  }
26
- prop[p] = get(prop, p, get(this.config, `defaults.property.${prop.type}.${p}`, def[p]))
26
+ prop[p] = get(prop, p, get(this.config, `default.property.${prop.type}.${p}`, def[p]))
27
27
  if (def.choices && !def.choices.includes(prop[p])) {
28
28
  this.fatal('Unsupported %s \'%s\' for \'%s@%s\'. Allowed choices: %s',
29
29
  p, prop[p], prop.name, schema.name, join(def.choices))
@@ -0,0 +1,8 @@
1
+ async function connSanitizer (connection) {
2
+ const { pick } = this.app.bajo.lib._
3
+ const result = pick(connection, ['type', 'name', 'driver'])
4
+ result.memory = true
5
+ return result
6
+ }
7
+
8
+ export default connSanitizer
@@ -0,0 +1,41 @@
1
+ let saving = false
2
+
3
+ async function instantiate ({ connection, schemas, noRebuild }) {
4
+ const { getPluginDataDir } = this.app.bajo
5
+ const { fs } = this.app.bajo.lib
6
+ const { pick } = this.app.bajo.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.name)}/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('Can\'t load %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.syncPeriod * 1000)
39
+ }
40
+
41
+ export default instantiate
@@ -0,0 +1,6 @@
1
+ async function clear ({ schema, options = {} }) {
2
+ this.memDb.storage[schema.name].splice(0)
3
+ return true
4
+ }
5
+
6
+ export default clear
@@ -0,0 +1,5 @@
1
+ async function exists ({ schema, options = {} }) {
2
+ this.memDb.storage[schema.name] = []
3
+ }
4
+
5
+ export default exists
@@ -0,0 +1,5 @@
1
+ async function exists ({ schema, options = {} }) {
2
+ this.memDb.storage[schema.name].splice(0)
3
+ }
4
+
5
+ export default exists
@@ -0,0 +1,5 @@
1
+ async function exists ({ schema, options = {} }) {
2
+ return true
3
+ }
4
+
5
+ export default exists
@@ -0,0 +1,12 @@
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('Record \'%s@%s\' exists already!', 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
@@ -0,0 +1,20 @@
1
+ import { Query } from 'mingo'
2
+
3
+ async function find ({ schema, filter = {}, options = {} }) {
4
+ const { prepPagination } = this.app.dobo
5
+ const { omit } = this.app.bajo.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
@@ -0,0 +1,9 @@
1
+ async function get ({ schema, id, options = {} }) {
2
+ const { thrownNotFound = true } = options
3
+ const { find } = this.app.bajo.lib._
4
+ const result = find(this.memDb.storage[schema.name], { id })
5
+ if (!result && thrownNotFound) throw this.error('Record \'%s@%s\' not found!', id, schema.name, { statusCode: 404 })
6
+ return { data: result }
7
+ }
8
+
9
+ export default get
@@ -0,0 +1,13 @@
1
+ import getRecord from './get.js'
2
+
3
+ async function remove ({ schema, id, options = {} }) {
4
+ const { noResult } = options
5
+ const { findIndex, pullAt } = this.app.bajo.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
@@ -0,0 +1,15 @@
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.bajo.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
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,25 @@
1
+ import addFixtures from '../add-fixtures.js'
2
+
3
+ async function start () {
4
+ const { filter, map } = this.app.bajo.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('Adding fixture for memory database')
13
+ for (const schema of schemas) {
14
+ if (schema.persistence) {
15
+ this.log.warn('\'%s\' is a memory persistence model. Adding records from fixture is ignored!', 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
@@ -3,10 +3,11 @@ async function resolveMethod (name, method, options = {}) {
3
3
  const { fs } = this.app.bajo.lib
4
4
  const { camelCase } = this.app.bajo.lib._
5
5
  const { schema, driver, connection } = this.getInfo(name)
6
- if (!options.force && (schema.disabled ?? []).includes(method)) throw this.error('Method \'%s@%s\' is disabled', camelCase(method), name)
7
- const cfg = this.app[driver.ns].config
8
6
  const [group, action] = method.split('-')
9
- const file = `${cfg.dir.pkg}/${this.name}/method/${group}/${action}.js`
7
+ if (!options.force && (schema.disabled ?? []).includes(action)) throw this.error('Method \'%s@%s\' is disabled', 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}/${this.name}/method/${group}/${action}.js`
10
11
  if (!fs.existsSync(file)) throw this.error('Method \'%s@%s\' is unsupported', camelCase(method), name)
11
12
  const handler = await importModule(file)
12
13
  return { handler, schema, driver, connection }