dobo 1.0.2 → 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.
- package/bajo/.alias +1 -0
- package/bajo/config.json +12 -2
- package/bajo/init.js +14 -3
- package/bajo/method/attachment/create.js +1 -1
- package/bajo/method/attachment/get-path.js +1 -1
- package/bajo/method/attachment/get.js +1 -1
- package/bajo/method/attachment/remove.js +1 -1
- package/bajo/method/build-query.js +1 -0
- package/bajo/method/bulk/create.js +6 -6
- package/bajo/method/get-connection.js +6 -0
- package/bajo/method/get-info.js +2 -2
- package/bajo/method/get-schema.js +2 -2
- package/bajo/method/model/clear.js +7 -5
- package/bajo/method/model/create.js +10 -2
- package/bajo/method/model/drop.js +10 -2
- package/bajo/method/model/exists.js +9 -2
- package/bajo/method/pick-record.js +14 -8
- package/bajo/method/prep-pagination.js +5 -9
- package/bajo/method/record/clear.js +8 -7
- package/bajo/method/record/create.js +26 -28
- package/bajo/method/record/find-one.js +10 -9
- package/bajo/method/record/find.js +11 -9
- package/bajo/method/record/get.js +10 -9
- package/bajo/method/record/remove.js +13 -12
- package/bajo/method/record/update.js +22 -25
- package/bajo/method/record/upsert.js +4 -3
- package/bajo/method/sanitize/body.js +8 -9
- package/bajo/method/stat/aggregate.js +6 -6
- package/bajo/method/stat/histogram.js +5 -5
- package/bajo/method/validate.js +23 -20
- package/bajo/start.js +6 -2
- package/bajoCli/applet/connection.js +1 -1
- package/bajoCli/applet/lib/post-process.js +13 -13
- package/bajoCli/applet/model-clear.js +2 -2
- package/bajoCli/applet/model-rebuild.js +12 -9
- package/bajoCli/applet/record-create.js +2 -2
- package/bajoCli/applet/record-find.js +2 -2
- package/bajoCli/applet/record-get.js +2 -2
- package/bajoCli/applet/record-remove.js +2 -2
- package/bajoCli/applet/record-update.js +2 -2
- package/bajoCli/applet/schema.js +1 -1
- package/bajoCli/applet/stat-count.js +2 -2
- package/bajoI18N/resource/en-US.json +28 -27
- package/bajoI18N/resource/id.json +60 -27
- package/lib/add-fixtures.js +4 -4
- package/lib/check-unique.js +2 -2
- package/lib/collect-connections.js +3 -4
- package/lib/collect-drivers.js +11 -3
- package/lib/collect-feature.js +6 -5
- package/lib/collect-schemas.js +9 -7
- package/lib/exec-validation.js +8 -14
- package/lib/generic-prop-sanitizer.js +1 -1
- package/lib/mem-db/conn-sanitizer.js +8 -0
- package/lib/mem-db/instantiate.js +41 -0
- package/lib/mem-db/method/model/clear.js +6 -0
- package/lib/mem-db/method/model/create.js +5 -0
- package/lib/mem-db/method/model/drop.js +5 -0
- package/lib/mem-db/method/model/exists.js +5 -0
- package/lib/mem-db/method/record/create.js +12 -0
- package/lib/mem-db/method/record/find.js +20 -0
- package/lib/mem-db/method/record/get.js +9 -0
- package/lib/mem-db/method/record/remove.js +13 -0
- package/lib/mem-db/method/record/update.js +15 -0
- package/lib/mem-db/method/stat/count.js +11 -0
- package/lib/mem-db/start.js +25 -0
- package/lib/resolve-method.js +4 -3
- package/lib/sanitize-schema.js +21 -9
- package/package.json +4 -3
- package/bajo/hook/bajoI18N@before-init.js +0 -6
- package/bajoCli/applet/shell.js +0 -48
- /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": "
|
|
56
|
-
"any.only": "
|
|
57
|
-
"string.alphanum": "
|
|
58
|
-
"string.base": "
|
|
59
|
-
"string.base64": "
|
|
60
|
-
"string.creditCard": "
|
|
61
|
-
"string.dataUri": "
|
|
62
|
-
"string.domain": "
|
|
63
|
-
"string.email": "
|
|
64
|
-
"string.empty": "
|
|
65
|
-
"string.guid": "
|
|
66
|
-
"string.hex": "
|
|
67
|
-
"string.hexAlign": "
|
|
68
|
-
"string.hostname": "
|
|
69
|
-
"string.ip": "
|
|
70
|
-
"string.ipVersion": "
|
|
71
|
-
"string.isoDate": "
|
|
72
|
-
"string.isoDuration": "
|
|
73
|
-
"string.length": "
|
|
74
|
-
"string.lowercase": "
|
|
75
|
-
"string.max": "
|
|
76
|
-
"string.min": "
|
|
77
|
-
"string.token": "
|
|
78
|
-
"string.trim": "
|
|
79
|
-
"string.uri": "
|
|
80
|
-
"string.uppercase": "
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"string
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
}
|
package/lib/add-fixtures.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
|
|
3
|
-
async function addFixture (name,
|
|
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
|
|
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
|
|
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 (
|
|
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))
|
package/lib/check-unique.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
package/lib/collect-drivers.js
CHANGED
|
@@ -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.
|
|
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.*',
|
|
36
|
+
}, { glob: 'boot/driver.*', prefix: this.name })
|
|
29
37
|
await runHook(`${this.name}:afterCollectDrivers`)
|
|
30
38
|
}
|
|
31
39
|
|
package/lib/collect-feature.js
CHANGED
|
@@ -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 !==
|
|
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
|
-
|
|
11
|
-
|
|
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',
|
|
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
|
|
package/lib/collect-schemas.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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', '
|
|
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/*.*',
|
|
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
|
}
|
package/lib/exec-validation.js
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
async function execValidation ({
|
|
1
|
+
async function execValidation ({ name, body, options, partial }) {
|
|
2
2
|
const { runHook } = this.app.bajo
|
|
3
|
-
const {
|
|
3
|
+
const { keys, camelCase } = this.app.bajo.lib._
|
|
4
|
+
const { noHook } = options
|
|
4
5
|
if (!noHook) {
|
|
5
|
-
await runHook(`${this.name}:
|
|
6
|
-
await runHook(`${this.name}.${name}:
|
|
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
|
-
|
|
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}:
|
|
22
|
-
await runHook(`${this.name}.${name}:
|
|
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, `
|
|
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,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,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
|
package/lib/resolve-method.js
CHANGED
|
@@ -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
|
-
|
|
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 }
|