dobo 2.0.1 → 2.2.1
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/.github/FUNDING.yml +0 -0
- package/.github/workflows/repo-lockdown.yml +0 -0
- package/.jsdoc.conf.json +0 -0
- package/LICENSE +0 -0
- package/README.md +2 -2
- package/docs/Dobo.html +0 -0
- package/docs/data/search.json +0 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/global.html +0 -0
- package/docs/index.html +0 -0
- package/docs/index.js.html +0 -0
- package/docs/lib_collect-connections.js.html +0 -0
- package/docs/lib_collect-drivers.js.html +0 -0
- package/docs/lib_collect-features.js.html +0 -0
- package/docs/lib_collect-schemas.js.html +0 -0
- package/docs/lib_index.js.html +0 -0
- package/docs/method_model_create.js.html +0 -0
- package/docs/method_model_drop.js.html +0 -0
- package/docs/method_model_exists.js.html +0 -0
- package/docs/method_record_count.js.html +0 -0
- package/docs/method_record_create.js.html +0 -0
- package/docs/method_record_find-all.js.html +0 -0
- package/docs/method_record_find-one.js.html +0 -0
- package/docs/method_record_find.js.html +0 -0
- package/docs/method_record_get.js.html +0 -0
- package/docs/method_record_remove.js.html +0 -0
- package/docs/method_record_update.js.html +0 -0
- package/docs/method_record_upsert.js.html +0 -0
- package/docs/method_sanitize_body.js.html +0 -0
- package/docs/method_sanitize_date.js.html +0 -0
- package/docs/method_sanitize_id.js.html +0 -0
- package/docs/method_validate.js.html +0 -0
- package/docs/module-Lib.html +0 -0
- package/docs/scripts/core.js +476 -477
- package/docs/scripts/core.min.js +0 -0
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/search.min.js +0 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +0 -0
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +282 -285
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1195 -1202
- package/docs/scripts/third-party/hljs.js +1 -1
- package/docs/scripts/third-party/popper.js +1 -1
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +508 -509
- package/docs/scripts/third-party/tocbot.min.js +0 -0
- package/docs/static/bitcoin.jpeg +0 -0
- package/docs/static/home.md +0 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +0 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +0 -0
- package/docs/styles/clean-jsdoc-theme-light.css +0 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -0
- package/docs/styles/clean-jsdoc-theme.min.css +0 -0
- package/extend/bajo/intl/en-US.json +66 -28
- package/extend/bajo/intl/id.json +55 -27
- package/extend/bajoCli/applet/clear-record.js +22 -0
- package/extend/bajoCli/applet/connection.js +0 -0
- package/extend/bajoCli/applet/count-record.js +27 -0
- package/extend/bajoCli/applet/create-aggregate.js +33 -0
- package/extend/bajoCli/applet/create-histogram.js +33 -0
- package/extend/bajoCli/applet/create-record.js +39 -0
- package/extend/bajoCli/applet/find-record.js +27 -0
- package/extend/bajoCli/applet/get-record.js +27 -0
- package/extend/bajoCli/applet/lib/post-process.js +10 -17
- package/extend/bajoCli/applet/model.js +22 -0
- package/extend/bajoCli/applet/rebuild-model.js +91 -0
- package/extend/bajoCli/applet/remove-record.js +27 -0
- package/extend/bajoCli/applet/update-record.js +44 -0
- package/extend/bajoCli/applet.js +0 -0
- package/extend/dobo/driver/memory.js +170 -0
- package/extend/dobo/feature/created-at.js +9 -7
- package/extend/dobo/feature/dt.js +0 -0
- package/extend/dobo/feature/immutable.js +30 -0
- package/extend/dobo/feature/int-id.js +0 -0
- package/extend/dobo/feature/removed-at.js +32 -54
- package/extend/dobo/feature/updated-at.js +14 -12
- package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +2 -6
- package/extend/waibuStatic/virtual.json +0 -0
- package/index.js +284 -371
- package/lib/collect-connections.js +49 -21
- package/lib/collect-drivers.js +19 -33
- package/lib/collect-features.js +24 -17
- package/lib/collect-models.js +321 -0
- package/lib/factory/action.js +161 -0
- package/lib/factory/connection.js +62 -0
- package/lib/factory/driver.js +372 -0
- package/lib/factory/feature.js +33 -0
- package/lib/factory/model/_util.js +402 -0
- package/lib/factory/model/build.js +15 -0
- package/lib/factory/model/clear-record.js +17 -0
- package/lib/factory/model/count-record.js +17 -0
- package/lib/factory/model/create-aggregate.js +17 -0
- package/lib/factory/model/create-attachment.js +29 -0
- package/lib/factory/model/create-histogram.js +17 -0
- package/lib/factory/model/create-record.js +35 -0
- package/lib/factory/model/drop.js +15 -0
- package/lib/factory/model/exists.js +21 -0
- package/lib/factory/model/find-all-record.js +71 -0
- package/lib/factory/model/find-attachment.js +29 -0
- package/lib/factory/model/find-one-record.js +19 -0
- package/{method/record/find.js → lib/factory/model/find-record.js} +103 -115
- package/lib/factory/model/get-attachment.js +15 -0
- package/lib/factory/model/get-record.js +79 -0
- package/lib/factory/model/list-attachment.js +37 -0
- package/lib/{add-fixtures.js → factory/model/load-fixtures.js} +69 -67
- package/lib/factory/model/remove-attachment.js +15 -0
- package/lib/factory/model/remove-record.js +59 -0
- package/lib/factory/model/sanitize-body.js +56 -0
- package/lib/factory/model/sanitize-id.js +7 -0
- package/lib/factory/model/sanitize-record.js +26 -0
- package/lib/factory/model/update-attachment.js +9 -0
- package/lib/factory/model/update-record.js +81 -0
- package/lib/factory/model/upsert-record.js +95 -0
- package/{method → lib/factory/model}/validate.js +38 -52
- package/lib/factory/model.js +150 -0
- package/lib/index.js +0 -0
- package/package.json +8 -4
- package/wiki/APPLETS.md +0 -0
- package/wiki/CHANGES.md +50 -0
- package/wiki/CONFIG.md +0 -0
- package/wiki/CONTRIBUTING.md +0 -0
- package/wiki/DEV-GUIDE.md +0 -0
- package/wiki/ECOSYSTEM.md +0 -0
- package/wiki/GETTING-STARTED.md +10 -10
- package/wiki/QUERY-LANGUAGE.md +0 -0
- package/wiki/USER-GUIDE.md +0 -0
- package/extend/bajoCli/applet/model-clear.js +0 -11
- package/extend/bajoCli/applet/model-rebuild.js +0 -101
- package/extend/bajoCli/applet/record-create.js +0 -43
- package/extend/bajoCli/applet/record-find.js +0 -28
- package/extend/bajoCli/applet/record-get.js +0 -24
- package/extend/bajoCli/applet/record-remove.js +0 -24
- package/extend/bajoCli/applet/record-update.js +0 -47
- package/extend/bajoCli/applet/schema.js +0 -22
- package/extend/bajoCli/applet/stat-count.js +0 -24
- package/lib/build-bulk-action.js +0 -12
- package/lib/check-unique.js +0 -39
- package/lib/collect-schemas.js +0 -91
- package/lib/exec-feature-hook.js +0 -13
- package/lib/exec-validation.js +0 -21
- package/lib/generic-prop-sanitizer.js +0 -32
- package/lib/handle-attachment-upload.js +0 -16
- package/lib/mem-db/conn-sanitizer.js +0 -8
- package/lib/mem-db/instantiate.js +0 -41
- package/lib/mem-db/method/model/clear.js +0 -6
- package/lib/mem-db/method/model/create.js +0 -5
- package/lib/mem-db/method/model/drop.js +0 -5
- package/lib/mem-db/method/model/exists.js +0 -5
- package/lib/mem-db/method/record/create.js +0 -12
- package/lib/mem-db/method/record/find.js +0 -20
- package/lib/mem-db/method/record/get.js +0 -9
- package/lib/mem-db/method/record/remove.js +0 -13
- package/lib/mem-db/method/record/update.js +0 -15
- package/lib/mem-db/method/stat/count.js +0 -11
- package/lib/mem-db/start.js +0 -25
- package/lib/merge-attachment-info.js +0 -16
- package/lib/multi-rel-rows.js +0 -42
- package/lib/resolve-method.js +0 -16
- package/lib/sanitize-schema.js +0 -198
- package/lib/single-rel-rows.js +0 -38
- package/method/attachment/copy-uploaded.js +0 -34
- package/method/attachment/create.js +0 -29
- package/method/attachment/find.js +0 -27
- package/method/attachment/get-path.js +0 -12
- package/method/attachment/get.js +0 -12
- package/method/attachment/pre-check.js +0 -9
- package/method/attachment/remove.js +0 -11
- package/method/attachment/update.js +0 -7
- package/method/bulk/create.js +0 -46
- package/method/model/clear.js +0 -22
- package/method/model/create.js +0 -32
- package/method/model/drop.js +0 -31
- package/method/model/exists.js +0 -37
- package/method/record/clear.js +0 -24
- package/method/record/count.js +0 -66
- package/method/record/create.js +0 -111
- package/method/record/find-all.js +0 -41
- package/method/record/find-one.js +0 -70
- package/method/record/get.js +0 -89
- package/method/record/remove.js +0 -72
- package/method/record/update.js +0 -104
- package/method/record/upsert.js +0 -51
- package/method/sanitize/body.js +0 -85
- package/method/sanitize/date.js +0 -27
- package/method/sanitize/id.js +0 -17
- package/method/stat/aggregate.js +0 -23
- package/method/stat/histogram.js +0 -26
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
async function model (path, ...args) {
|
|
2
|
+
const { importPkg } = this.app.bajo
|
|
3
|
+
const { isEmpty, map, find } = this.app.lib._
|
|
4
|
+
const { getOutputFormat, writeOutput } = this.app.bajoCli
|
|
5
|
+
const select = await importPkg('bajoCli:@inquirer/select')
|
|
6
|
+
const format = getOutputFormat()
|
|
7
|
+
if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
|
|
8
|
+
let name = args[0]
|
|
9
|
+
if (isEmpty(name)) {
|
|
10
|
+
const choices = map(this.models, s => ({ value: s.name }))
|
|
11
|
+
name = await select({
|
|
12
|
+
message: this.print.buildText('selectModel'),
|
|
13
|
+
choices
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
const result = find(this.models, { name })
|
|
17
|
+
if (!result) return this.print.fail('cantFind%s%s', this.t('model'), name, { exit: this.app.applet })
|
|
18
|
+
this.print.info('done')
|
|
19
|
+
await writeOutput(result, path, format)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default model
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
async function modelRebuild (path, ...args) {
|
|
2
|
+
const { importPkg } = this.app.bajo
|
|
3
|
+
const { outmatch } = this.app.lib
|
|
4
|
+
const { isEmpty, map, trim, without } = this.app.lib._
|
|
5
|
+
const [input, confirm, boxen] = await importPkg('bajoCli:@inquirer/input',
|
|
6
|
+
'bajoCli:@inquirer/confirm', 'bajoCli:boxen')
|
|
7
|
+
const models = map(this.models, 'name')
|
|
8
|
+
let names = args.join(' ')
|
|
9
|
+
if (isEmpty(models)) return this.print.fail('notFound%s', 'model', { exit: this.app.applet })
|
|
10
|
+
if (isEmpty(names)) {
|
|
11
|
+
names = await input({
|
|
12
|
+
message: this.print.buildText('enterModelName'),
|
|
13
|
+
default: '*'
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
const isMatch = outmatch(map(names.split(' '), m => trim(m)))
|
|
17
|
+
names = models.filter(isMatch)
|
|
18
|
+
if (names.length === 0) return this.print.fail('No model matched', true, { exit: this.app.applet })
|
|
19
|
+
console.log(boxen(names.join(' '), { title: this.t('model%d', names.length), padding: 0.5, borderStyle: 'round' }))
|
|
20
|
+
const answer = await confirm({
|
|
21
|
+
message: this.print.buildText('modelsWillBeRebuiltContinue'),
|
|
22
|
+
default: false
|
|
23
|
+
})
|
|
24
|
+
if (!answer) return this.print.fail('aborted', { exit: this.app.applet })
|
|
25
|
+
/*
|
|
26
|
+
const conns = []
|
|
27
|
+
for (const s of names) {
|
|
28
|
+
const { connection } = this.getInfo(s)
|
|
29
|
+
if (!conns.includes(connection.name)) conns.push(connection.name)
|
|
30
|
+
}
|
|
31
|
+
*/
|
|
32
|
+
await this.start('all')
|
|
33
|
+
const result = { succed: 0, failed: 0, skipped: 0 }
|
|
34
|
+
const skipped = []
|
|
35
|
+
for (const s of names) {
|
|
36
|
+
const model = this.getModel(s)
|
|
37
|
+
const spin = this.print.spinner().start('rebuilding%s', model.name)
|
|
38
|
+
if (model.driver.memory) {
|
|
39
|
+
spin.warn('memoryDbSkipped%s', model.name)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
const exists = await model.exists({ spinner: spin })
|
|
43
|
+
if (exists) {
|
|
44
|
+
if (this.app.bajo.config.force) {
|
|
45
|
+
try {
|
|
46
|
+
await model.drop({ spinner: spin })
|
|
47
|
+
spin.setText('modelDropped%s', model.name)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
spin.fail('errorDroppingModel%s%s', model.name, err.message)
|
|
50
|
+
result.failed++
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
spin.fail('modelExistsNeedForce%s', model.name)
|
|
55
|
+
result.failed++
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await model.build({ spinner: spin })
|
|
61
|
+
spin.succeed('modelCreated%s', model.name)
|
|
62
|
+
result.succed++
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (this.app.bajo.config.log.applet) console.error(err)
|
|
65
|
+
spin.fail('errorCreatingModel%s%s', model.name, err.message)
|
|
66
|
+
result.failed++
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.print.info('succeedFailSkip%d%d%d', result.succed, result.failed, result.skipped)
|
|
70
|
+
if (result.failed > 0) this.print.fatal('cantContinueAddFixture')
|
|
71
|
+
for (const s of without(names, ...skipped)) {
|
|
72
|
+
const model = this.getModel(s)
|
|
73
|
+
const spin = this.print.spinner().start('addingFixture%s', model.name)
|
|
74
|
+
if (model.driver.memory) {
|
|
75
|
+
spin.warn('memoryDbSkipped%s', model.name)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const fixture = await model.loadFixtures({ spinner: spin })
|
|
80
|
+
spin.succeed('fixtureAdded%s%s%s', model.name, fixture.success, fixture.failed)
|
|
81
|
+
result.succed++
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (this.app.bajo.config.log.applet) console.error(err)
|
|
84
|
+
spin.fail('errorAddingFixture%s%s', model.name, err.message)
|
|
85
|
+
result.failed++
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.app.exit()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default modelRebuild
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import postProcess from './lib/post-process.js'
|
|
2
|
+
|
|
3
|
+
async function removeRecord (path, ...args) {
|
|
4
|
+
const { importPkg } = this.app.bajo
|
|
5
|
+
const { isEmpty, map } = this.app.lib._
|
|
6
|
+
const { parseKvString } = this.app.lib.aneka
|
|
7
|
+
const [input, select] = await importPkg('bajoCli:@inquirer/input', 'bajoCli:@inquirer/select')
|
|
8
|
+
if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
|
|
9
|
+
let [model, id, options] = args
|
|
10
|
+
options = isEmpty(options) ? {} : parseKvString(options)
|
|
11
|
+
if (isEmpty(model)) {
|
|
12
|
+
model = await select({
|
|
13
|
+
message: this.print.buildText('selectModel'),
|
|
14
|
+
choices: map(this.models, s => ({ value: s.name }))
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
if (isEmpty(id + '')) {
|
|
18
|
+
id = await input({
|
|
19
|
+
message: this.print.buildText('enterRecordId'),
|
|
20
|
+
validate: text => isEmpty(text) ? this.t('idIsRequired') : true
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
await postProcess.call(this, { noConfirmation: options.noConfirmation, handler: 'removeRecord', params: [model, id, options], path, processMsg: 'Removing record' })
|
|
24
|
+
this.app.exit()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default removeRecord
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import postProcess from './lib/post-process.js'
|
|
2
|
+
|
|
3
|
+
async function updateRecord (path, ...args) {
|
|
4
|
+
const { importPkg } = this.app.bajo
|
|
5
|
+
const { isEmpty, map } = this.app.lib._
|
|
6
|
+
const { parseKvString } = this.app.lib.aneka
|
|
7
|
+
const [input, select, boxen] = await importPkg('bajoCli:@inquirer/input',
|
|
8
|
+
'bajoCli:@inquirer/select', 'bajoCli:boxen')
|
|
9
|
+
if (isEmpty(this.models)) return this.print.fail('notFound%s', this.t('field.model'), { exit: this.app.applet })
|
|
10
|
+
let [model, id, body, options] = args
|
|
11
|
+
options = isEmpty(options) ? {} : parseKvString(options)
|
|
12
|
+
if (isEmpty(model)) {
|
|
13
|
+
model = await select({
|
|
14
|
+
message: this.print.buildText('selectModel'),
|
|
15
|
+
choices: map(this.models, s => ({ value: s.name }))
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
if (isEmpty(id + '')) {
|
|
19
|
+
id = await input({
|
|
20
|
+
message: this.print.buildText('enterRecordId'),
|
|
21
|
+
validate: text => isEmpty(text) ? this.t('idIsRequired') : true
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
if (isEmpty(body)) {
|
|
25
|
+
body = await input({
|
|
26
|
+
message: this.print.buildText('enterPayload'),
|
|
27
|
+
validate: text => {
|
|
28
|
+
if (isEmpty(text)) return this.t('payloadRequired')
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
let payload
|
|
34
|
+
try {
|
|
35
|
+
payload = body[0] === '{' ? JSON.parse(body) : parseKvString(body)
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return this.print.fail('invalidPayloadSyntax', { exit: this.app.applet })
|
|
38
|
+
}
|
|
39
|
+
console.log(boxen(JSON.stringify(payload, null, 2), { title: model, padding: 0.5, borderStyle: 'round' }))
|
|
40
|
+
await postProcess.call(this, { noConfirmation: options.noConfirmation, handler: 'updateRecord', params: [model, id, payload, options], path, processMsg: 'Updating record' })
|
|
41
|
+
this.app.exit()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default updateRecord
|
package/extend/bajoCli/applet.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Query } from 'mingo'
|
|
2
|
+
|
|
3
|
+
async function memoryDriverFactory () {
|
|
4
|
+
const { DoboDriver } = this.app.baseClass
|
|
5
|
+
const { findIndex, pullAt, omit, has } = this.app.lib._
|
|
6
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
7
|
+
|
|
8
|
+
class DoboMemoryDriver extends DoboDriver {
|
|
9
|
+
constructor (plugin, name, options) {
|
|
10
|
+
super(plugin, name, options)
|
|
11
|
+
this.idGenerator = 'ulid'
|
|
12
|
+
this.saving = true
|
|
13
|
+
this.memory = true
|
|
14
|
+
this.autoSave = []
|
|
15
|
+
this.storage = {}
|
|
16
|
+
this.support = {
|
|
17
|
+
propType: {
|
|
18
|
+
object: true,
|
|
19
|
+
array: true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async _loadFromFile (model, dir) {
|
|
25
|
+
const { fs } = this.app.lib
|
|
26
|
+
this.autoSave.push(model.name)
|
|
27
|
+
const file = `${dir}/${model.name}.json`
|
|
28
|
+
if (!fs.existsSync(file)) return
|
|
29
|
+
try {
|
|
30
|
+
const data = fs.readFileSync(file, 'utf8')
|
|
31
|
+
this.storage[model.name] = JSON.parse(data)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
this.fatal('cantLoad%s%s', model.name, err.message)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async sanitizeConnection (conn) {
|
|
38
|
+
await super.sanitizeConnection(conn)
|
|
39
|
+
conn.memory = true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async connect (connection, noRebuild) {
|
|
43
|
+
const conn = this.plugin.getConnection('memory')
|
|
44
|
+
const models = this.plugin.getModelsByConnection(conn.name)
|
|
45
|
+
const { getPluginDataDir } = this.app.bajo
|
|
46
|
+
const { fs } = this.app.lib
|
|
47
|
+
const pdir = `${getPluginDataDir(this.plugin.ns)}/memDb/data` // persistence dir
|
|
48
|
+
fs.ensureDirSync(pdir)
|
|
49
|
+
conn.autoSave = conn.autoSave ?? []
|
|
50
|
+
for (const model of models) {
|
|
51
|
+
this.storage[model.name] = this.storage[model.name] ?? [] // init empty model
|
|
52
|
+
if (conn.autoSave.includes(model.name)) await this._loadFromFile(model, pdir)
|
|
53
|
+
await model.loadFixtures()
|
|
54
|
+
}
|
|
55
|
+
if (conn.autoSave.length === 0) return
|
|
56
|
+
setInterval(() => {
|
|
57
|
+
if (!this.saving) return
|
|
58
|
+
this.saving = true
|
|
59
|
+
for (const item of conn.autoSave) {
|
|
60
|
+
const data = this.storage[item]
|
|
61
|
+
fs.writeFileSync(`${pdir}/${item}.json`, JSON.stringify(data), 'utf8')
|
|
62
|
+
}
|
|
63
|
+
this.saving = false
|
|
64
|
+
}, this.plugin.config.memDb.autoSaveDur)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async _getOldRecord (model, id, options = {}) {
|
|
68
|
+
const idx = findIndex(this.storage[model.name], { _id: id })
|
|
69
|
+
const oldData = this.storage[model.name][idx]
|
|
70
|
+
return { idx, oldData }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async modelExists (model, options = {}) {
|
|
74
|
+
return { data: has(this.storage, model.name) }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async buildModel (model, options = {}) {
|
|
78
|
+
if (has(this.storage, model.name)) throw this.plugin.error('exist%s%s', this.plugin.t('model'), model.name)
|
|
79
|
+
this.storage[model.name] = []
|
|
80
|
+
if (options.noResult) return
|
|
81
|
+
return { data: true }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async dropModel (model, options = {}) {
|
|
85
|
+
if (!has(this.storage, model.name)) throw this.plugin.error('notFound%s%s', this.plugin.t('model'), model.name)
|
|
86
|
+
delete this.storage[model.name]
|
|
87
|
+
if (options.noResult) return
|
|
88
|
+
return { data: true }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async createRecord (model, body = {}, options = {}) {
|
|
92
|
+
this.storage[model.name].push(body)
|
|
93
|
+
return { data: body }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getRecord (model, id, options = {}) {
|
|
97
|
+
const { oldData: data } = await this._getOldRecord(model, id)
|
|
98
|
+
return { data }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async updateRecord (model, id, body = {}, options = {}) {
|
|
102
|
+
const { idx, oldData } = await this._getOldRecord(model, id)
|
|
103
|
+
const data = defaultsDeep(body, oldData)
|
|
104
|
+
this.storage[model.name][idx] = data
|
|
105
|
+
return { oldData, data }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async removeRecord (model, id, options = {}) {
|
|
109
|
+
const { idx, oldData } = await this._getOldRecord(model, id)
|
|
110
|
+
pullAt(this.storage[model.name], idx)
|
|
111
|
+
return { oldData }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async clearRecord (model, options = {}) {
|
|
115
|
+
this.storage[model.name] = []
|
|
116
|
+
if (options.noResult) return
|
|
117
|
+
return { data: true }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async findRecord (model, filter = {}, options = {}) {
|
|
121
|
+
const { limit, skip, sort, page } = filter
|
|
122
|
+
const { data: count = 0 } = await this.countRecord(model, filter, options)
|
|
123
|
+
const cursor = this._getCursor(model, filter)
|
|
124
|
+
if (sort) cursor.sort(sort)
|
|
125
|
+
if (!options.noLimit) cursor.skip(skip).limit(limit)
|
|
126
|
+
let result = { data: cursor.all(), page, limit, count, pages: Math.ceil(count / limit) }
|
|
127
|
+
if (!options.count) result = omit(result, ['count', 'pages'])
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async findAllRecord (model, filter = {}, options = {}) {
|
|
132
|
+
const { sort } = filter
|
|
133
|
+
const { data: count = 0 } = await this.countRecord(model, filter, options)
|
|
134
|
+
const cursor = this._getCursor(model, filter)
|
|
135
|
+
if (sort) cursor.sort(sort)
|
|
136
|
+
let result = { data: cursor.all(), count }
|
|
137
|
+
if (!options.count) result = omit(result, ['count'])
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async countRecord (model, filter = {}, options = {}) {
|
|
142
|
+
const cursor = this._getCursor(model, filter)
|
|
143
|
+
const data = cursor.all().length
|
|
144
|
+
return { data }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async createAggregate (model, filter = {}, params = {}, options = {}) {
|
|
148
|
+
const item = await this.findAllRecord(model, filter, options)
|
|
149
|
+
const result = this.app.dobo.calcAggregate({ data: item.data, ...params })
|
|
150
|
+
return { data: result }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async createHistogram (model, filter = {}, params = {}, options = {}) {
|
|
154
|
+
const item = await this.findAllRecord(model, filter, options)
|
|
155
|
+
const result = this.app.dobo.calcHistogram({ data: item.data, ...params })
|
|
156
|
+
return { data: result }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_getCursor (model, filter) {
|
|
160
|
+
const criteria = filter.query ?? {}
|
|
161
|
+
const q = new Query(criteria, { idKey: '_id' })
|
|
162
|
+
return q.find(this.storage[model.name])
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.app.baseClass.DoboMemoryDriver = DoboMemoryDriver
|
|
167
|
+
return DoboMemoryDriver
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default memoryDriverFactory
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
async function createdAt (opts = {}) {
|
|
2
2
|
opts.fieldName = opts.fieldName ?? 'createdAt'
|
|
3
|
+
opts.noOverwrite = opts.noOverwrite ?? false
|
|
3
4
|
return {
|
|
4
|
-
properties: {
|
|
5
|
+
properties: [{
|
|
5
6
|
name: opts.fieldName,
|
|
6
7
|
type: 'datetime',
|
|
7
8
|
index: true
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
}],
|
|
10
|
+
hooks: [{
|
|
11
|
+
name: 'beforeCreateRecord',
|
|
12
|
+
handler: async function (body, options) {
|
|
11
13
|
const { isSet } = this.app.lib.aneka
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
+
if (opts.noOverwrite) body[opts.fieldName] = new Date()
|
|
15
|
+
else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
|
|
14
16
|
}
|
|
15
|
-
}
|
|
17
|
+
}]
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
async function beforeRemoveRecord (id, opts) {
|
|
2
|
+
const { get } = this.app.lib._
|
|
3
|
+
const record = await this.driver.getRecord(this, id)
|
|
4
|
+
const immutable = get(record.data, opts.fieldName)
|
|
5
|
+
if (immutable) throw this.plugin.error('recordImmutable%s%s', id, this.name, { statusCode: 423 })
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function immutable (opts = {}) {
|
|
9
|
+
opts.fieldName = opts.fieldName ?? '_immutable'
|
|
10
|
+
return {
|
|
11
|
+
properties: {
|
|
12
|
+
name: opts.fieldName,
|
|
13
|
+
type: 'boolean',
|
|
14
|
+
hidden: true
|
|
15
|
+
},
|
|
16
|
+
hooks: [{
|
|
17
|
+
name: 'beforeUpdateRecord',
|
|
18
|
+
handler: async function (id, body, options) {
|
|
19
|
+
await beforeRemoveRecord.call(this, id, opts)
|
|
20
|
+
}
|
|
21
|
+
}, {
|
|
22
|
+
name: 'beforeRemoveRecord',
|
|
23
|
+
handler: async function (id, options) {
|
|
24
|
+
await beforeRemoveRecord.call(this, id, opts)
|
|
25
|
+
}
|
|
26
|
+
}]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default immutable
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
async function
|
|
1
|
+
async function beforeFindRecord ({ filter = {} }, opts) {
|
|
2
2
|
filter.query = filter.query ?? {}
|
|
3
3
|
const { isEmpty, set } = this.app.lib._
|
|
4
4
|
const q = { $and: [] }
|
|
@@ -10,75 +10,53 @@ async function beforeFind ({ filter = {}, options }, opts) {
|
|
|
10
10
|
filter.query = q
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
async function
|
|
14
|
-
for (const rec of records.data) {
|
|
15
|
-
delete rec[opts.fieldName]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function afterGet ({ schema, id, record }, opts) {
|
|
13
|
+
async function afterGetRecord ({ id, record = {} }, opts) {
|
|
20
14
|
const { isEmpty } = this.app.lib._
|
|
21
|
-
if (!isEmpty(record.data[opts.fieldName])) throw this.error('recordNotFound%s%s', id,
|
|
22
|
-
delete record.data[opts.fieldName]
|
|
15
|
+
if (!isEmpty(record.data[opts.fieldName])) throw this.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
async function
|
|
18
|
+
async function beforeCreateRecord ({ body = {} }, opts) {
|
|
26
19
|
delete body[opts.fieldName]
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
async function afterCreate ({ record }, opts) {
|
|
30
|
-
delete record.data[opts.fieldName]
|
|
31
|
-
if (record.oldData) delete record.oldData[opts.fieldName]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
22
|
async function removedAt (opts = {}) {
|
|
35
|
-
opts.fieldName = opts.fieldName ?? '
|
|
23
|
+
opts.fieldName = opts.fieldName ?? '_removedAt'
|
|
36
24
|
return {
|
|
37
25
|
properties: {
|
|
38
26
|
name: opts.fieldName,
|
|
39
27
|
type: 'datetime',
|
|
40
|
-
index: true
|
|
28
|
+
index: true,
|
|
29
|
+
hidden: true
|
|
41
30
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
await
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await beforeCreate.call(this, { body }, opts)
|
|
66
|
-
},
|
|
67
|
-
afterUpdate: async function ({ record }) {
|
|
68
|
-
await afterCreate.call(this, { record }, opts)
|
|
69
|
-
},
|
|
70
|
-
beforeRemove: async function ({ schema, id, options }) {
|
|
71
|
-
const { recordUpdate, recordGet } = this.app.dobo
|
|
72
|
-
await recordGet(schema.name, id, options)
|
|
31
|
+
hooks: [{
|
|
32
|
+
name: 'beforeFindRecord',
|
|
33
|
+
handler: async function (filter, options) {
|
|
34
|
+
await beforeFindRecord.call(this, { filter, options }, opts)
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
name: 'afterGetRecord',
|
|
38
|
+
handler: async function (id, record, options) {
|
|
39
|
+
await afterGetRecord.call(this, { record }, opts)
|
|
40
|
+
}
|
|
41
|
+
}, {
|
|
42
|
+
name: 'beforeCreateRecord',
|
|
43
|
+
handler: async function (body, options) {
|
|
44
|
+
await beforeCreateRecord.call(this, { body }, opts)
|
|
45
|
+
}
|
|
46
|
+
}, {
|
|
47
|
+
name: 'beforeUpdateRecord',
|
|
48
|
+
handler: async function (id, body, options) {
|
|
49
|
+
await beforeCreateRecord.call(this, { body }, opts)
|
|
50
|
+
}
|
|
51
|
+
}, {
|
|
52
|
+
name: 'beforeRemoveRecord',
|
|
53
|
+
handler: async function (id, options) {
|
|
73
54
|
const { set } = this.app.lib._
|
|
74
55
|
const body = set({}, opts.fieldName, new Date())
|
|
75
|
-
const record = await recordUpdate(
|
|
56
|
+
const record = await this.driver.recordUpdate(this, id, body, { noResult: false })
|
|
76
57
|
options.record = { oldData: record.oldData }
|
|
77
|
-
},
|
|
78
|
-
afterRemove: async function ({ record }) {
|
|
79
|
-
delete record.oldData[opts.fieldName]
|
|
80
58
|
}
|
|
81
|
-
}
|
|
59
|
+
}]
|
|
82
60
|
}
|
|
83
61
|
}
|
|
84
62
|
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
async function updatedAt (opts = {}) {
|
|
2
|
+
const { isSet } = this.app.lib.aneka
|
|
2
3
|
opts.fieldName = opts.fieldName ?? 'updatedAt'
|
|
3
|
-
opts.
|
|
4
|
+
opts.noOverwrite = opts.noOverwrite ?? false
|
|
4
5
|
return {
|
|
5
6
|
properties: {
|
|
6
7
|
name: opts.fieldName,
|
|
7
8
|
type: 'datetime',
|
|
8
9
|
index: true
|
|
9
10
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
},
|
|
16
|
-
beforeUpdate: async function ({ body }) {
|
|
17
|
-
const { isSet } = this.app.lib.aneka
|
|
18
|
-
const now = new Date()
|
|
19
|
-
if (opts.overwrite || !isSet(body[opts.fieldName])) body[opts.fieldName] = now
|
|
11
|
+
hooks: [{
|
|
12
|
+
name: 'beforeCreateRecord',
|
|
13
|
+
handler: async function (body, options) {
|
|
14
|
+
if (opts.noOverwrite) body[opts.fieldName] = new Date()
|
|
15
|
+
else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
|
|
20
16
|
}
|
|
21
|
-
}
|
|
17
|
+
}, {
|
|
18
|
+
name: 'beforeUpdateRecord',
|
|
19
|
+
handler: async function (id, body, options) {
|
|
20
|
+
if (opts.noOverwrite) body[opts.fieldName] = new Date()
|
|
21
|
+
else if (!isSet(body[opts.fieldName])) body[opts.fieldName] = new Date()
|
|
22
|
+
}
|
|
23
|
+
}]
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -5,12 +5,8 @@ async function attachment (req, reply) {
|
|
|
5
5
|
const { pascalCase } = this.app.lib.aneka
|
|
6
6
|
const { routePath } = this.app.waibu
|
|
7
7
|
const { fs } = this.app.lib
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
id: req.params.id,
|
|
11
|
-
field: req.params.field,
|
|
12
|
-
file: '*'
|
|
13
|
-
})
|
|
8
|
+
const mdl = this.app.dobo.getModel(req.params.model)
|
|
9
|
+
const items = await mdl.listAttachments(req.params.id, req.params.field, '*')
|
|
14
10
|
let item = req.params.file === '_first' ? items[0] : undefined
|
|
15
11
|
if (!item) {
|
|
16
12
|
item = find(items, i => {
|
|
File without changes
|