bajo 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/class/app/cache.js +90 -0
- package/class/app.js +6 -0
- package/class/bajo.js +18 -24
- package/class/helper/bajo.js +18 -4
- package/class/helper/base.js +31 -14
- package/package.json +1 -1
- package/wiki/CHANGES.md +12 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class Cache {
|
|
2
|
+
constructor (app) {
|
|
3
|
+
this.app = app
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
getRootDir = () => {
|
|
7
|
+
const { getPluginDataDir } = this.app.bajo
|
|
8
|
+
return `${getPluginDataDir('bajo')}/cache`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
prep = (name, ttlDur = 0) => {
|
|
12
|
+
const { breakNsPath } = this.app.bajo
|
|
13
|
+
const { aneka, fs } = this.app.lib
|
|
14
|
+
const { ns, subNs, path } = breakNsPath(name)
|
|
15
|
+
ttlDur = aneka.parseDuration(ttlDur)
|
|
16
|
+
if (ttlDur === 0 || !subNs) return
|
|
17
|
+
const cacheDir = `${this.getRootDir()}/${ns}/${subNs}`
|
|
18
|
+
const dir = `${cacheDir}/${ttlDur}`
|
|
19
|
+
fs.ensureDirSync(dir)
|
|
20
|
+
const file = `${dir}/${path}`
|
|
21
|
+
return { dir, file, cacheDir }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
load = async (name, ttlDur = 0) => {
|
|
25
|
+
const { fs } = this.app.lib
|
|
26
|
+
const { dir, file } = this.prep(name, ttlDur) ?? {}
|
|
27
|
+
if (!file) return
|
|
28
|
+
if (!fs.existsSync(file)) return
|
|
29
|
+
const { mtimeMs } = await fs.stat(dir)
|
|
30
|
+
if (Date.now() - mtimeMs > ttlDur) {
|
|
31
|
+
await fs.remove(dir)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
let content = fs.readFileSync(file, 'utf8')
|
|
35
|
+
try {
|
|
36
|
+
if (['{', '['].includes(content[0]) && ['}', ']'].includes(content[content.length - 1])) content = JSON.parse(content)
|
|
37
|
+
} catch (err) {}
|
|
38
|
+
return content
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
save = async (name, item, ttlDur = 0) => {
|
|
42
|
+
const { fs } = this.app.lib
|
|
43
|
+
const { cloneDeep, isArray, isPlainObject } = this.app.lib._
|
|
44
|
+
const { dir, file } = this.prep(name, ttlDur) ?? {}
|
|
45
|
+
if (!file || !item) return
|
|
46
|
+
fs.ensureDirSync(dir)
|
|
47
|
+
let content = cloneDeep(item)
|
|
48
|
+
if (isArray(item) || isPlainObject(item)) content = JSON.stringify(content)
|
|
49
|
+
fs.writeFileSync(file, content, 'utf8')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sync = async (name, item, ttlDur = 0) => {
|
|
53
|
+
const content = await this.loadCache(name, ttlDur)
|
|
54
|
+
if (!content) await this.saveCache(name, item, ttlDur)
|
|
55
|
+
return content
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_purgeItem = (name) => {
|
|
59
|
+
if (!this.app.bajo) return
|
|
60
|
+
const { fs, fastGlob } = this.app.lib
|
|
61
|
+
try {
|
|
62
|
+
if (name === '*') {
|
|
63
|
+
const dirs = fastGlob.globSync(`${this.getRootDir()}/*`, { onlyDirectories: true })
|
|
64
|
+
for (const dir of dirs) {
|
|
65
|
+
fs.removeSync(dir)
|
|
66
|
+
}
|
|
67
|
+
} else fs.removeSync(`${this.getRootDir()}/${name}`)
|
|
68
|
+
} catch (err) {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
purge = (name) => {
|
|
72
|
+
if (!this.app.bajo) return
|
|
73
|
+
if (name) return this._purgeItem(name)
|
|
74
|
+
const { fastGlob, fs } = this.app.lib
|
|
75
|
+
const dirs = fastGlob.globSync(`${this.getRootDir()}/*/*/*`, { onlyDirectories: true })
|
|
76
|
+
for (const dir of dirs) {
|
|
77
|
+
try {
|
|
78
|
+
const ttlDur = Number(dir.split('/').pop())
|
|
79
|
+
const { mtimeMs } = fs.statSync(dir)
|
|
80
|
+
if (Date.now() - mtimeMs > ttlDur) fs.removeSync(dir)
|
|
81
|
+
} catch (err) {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dispose = async () => {
|
|
86
|
+
this.app = null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default Cache
|
package/class/app.js
CHANGED
|
@@ -2,6 +2,7 @@ import util from 'util'
|
|
|
2
2
|
import Bajo from './bajo.js'
|
|
3
3
|
import Base from './base.js'
|
|
4
4
|
import { runAsApplet } from './helper/bajo.js'
|
|
5
|
+
import Cache from './app/cache.js'
|
|
5
6
|
import Tools from './plugin/tools.js'
|
|
6
7
|
|
|
7
8
|
import { outmatchNs, parseObject, lib } from './helper/app.js'
|
|
@@ -196,6 +197,8 @@ class App {
|
|
|
196
197
|
*/
|
|
197
198
|
this.envVars = {}
|
|
198
199
|
|
|
200
|
+
this.cache = new Cache(this)
|
|
201
|
+
|
|
199
202
|
if (!options.cwd) options.cwd = process.cwd()
|
|
200
203
|
const l = last(process.argv)
|
|
201
204
|
if (l.startsWith('--cwd')) {
|
|
@@ -278,6 +281,9 @@ class App {
|
|
|
278
281
|
this.applet = this.envVars._.applet ?? this.argv._.applet
|
|
279
282
|
await this.bajo.runHook('bajo:beforeBoot')
|
|
280
283
|
await this.bajo.init()
|
|
284
|
+
// cache
|
|
285
|
+
this.cache.purge()
|
|
286
|
+
setInterval(this.cache.purge, this.bajo.config.cache.purgeIntvDur)
|
|
281
287
|
// boot complete
|
|
282
288
|
const elapsed = new Date() - this.runAt
|
|
283
289
|
this.bajo.log.debug('bootCompleted%s', secToHms(elapsed, true))
|
package/class/bajo.js
CHANGED
|
@@ -341,23 +341,20 @@ class Bajo extends Plugin {
|
|
|
341
341
|
* @returns {any}
|
|
342
342
|
*/
|
|
343
343
|
eachPlugins = async (handler, options = {}) => {
|
|
344
|
-
if (
|
|
345
|
-
const { glob, useBajo, prefix = '', noUnderscore = true, returnItems } = options
|
|
344
|
+
if (isString(options)) options = { glob: options }
|
|
345
|
+
const { glob = [], useBajo, prefix = '', noUnderscore = true, returnItems, opts = {} } = options
|
|
346
|
+
const globs = isString(glob) ? [glob] : [...glob]
|
|
346
347
|
const pluginPkgs = useBajo ? [...cloneDeep(this.app.pluginPkgs), 'bajo'] : this.app.pluginPkgs
|
|
347
348
|
const result = {}
|
|
348
349
|
for (const pkgName of pluginPkgs) {
|
|
349
350
|
const ns = camelCase(pkgName)
|
|
350
351
|
let r
|
|
351
|
-
if (
|
|
352
|
+
if (globs.length > 0) {
|
|
352
353
|
const base = prefix === '' ? `${this.app[ns].dir.pkg}/extend` : `${this.app[ns].dir.pkg}/extend/${prefix}`
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
for (const i in pattern) {
|
|
358
|
-
if (!path.isAbsolute(pattern[i])) pattern[i] = `${base}/${pattern[i]}`
|
|
359
|
-
}
|
|
360
|
-
const files = await fastGlob(pattern, opts)
|
|
354
|
+
const patterns = globs.map(glob => {
|
|
355
|
+
return !path.isAbsolute(glob) ? `${base}/${glob}` : glob
|
|
356
|
+
})
|
|
357
|
+
const files = await fastGlob.glob(patterns, opts)
|
|
361
358
|
for (const f of files) {
|
|
362
359
|
if (path.basename(f)[0] === '_' && noUnderscore) continue
|
|
363
360
|
const resp = await handler.call(this.app[ns], { file: f, dir: base })
|
|
@@ -850,7 +847,7 @@ class Bajo extends Plugin {
|
|
|
850
847
|
const { parseObject } = this.app.lib
|
|
851
848
|
const { defaultsDeep } = this.app.lib.aneka
|
|
852
849
|
const { uniq, isString, isArray, findIndex, isPlainObject, merge } = this.app.lib._
|
|
853
|
-
let { ns, baseNs, extend, checkOverride, merge: merged, pattern, ignoreError = true, defValue = {}, parserOpts = {}, globOpts = {}, handler } = options
|
|
850
|
+
let { ns, baseNs, extend, checkOverride, merge: merged, pattern, ignoreError = true, defValue = {}, parserOpts = {}, globOpts = {}, handler, cache = {} } = options
|
|
854
851
|
|
|
855
852
|
const getParseOptsArgs = (opts, orig) => {
|
|
856
853
|
opts.parserOpts = opts.parserOpts ?? {}
|
|
@@ -866,6 +863,7 @@ class Bajo extends Plugin {
|
|
|
866
863
|
let orig = parseObject(obj)
|
|
867
864
|
if (!baseNs || extend === false) {
|
|
868
865
|
await this.runHook('bajo:afterReadConfig', file, orig, options)
|
|
866
|
+
if (cache.name) await this.app.cache.save(cache.name, orig, cache.ttlDur)
|
|
869
867
|
return orig
|
|
870
868
|
}
|
|
871
869
|
const { suffix = '', keys = [] } = options
|
|
@@ -907,9 +905,13 @@ class Bajo extends Plugin {
|
|
|
907
905
|
let result = isArray(orig) ? [...orig, ...ext] : binder({}, keys.length > 0 ? pick(ext, keys) : ext, orig)
|
|
908
906
|
if (handler) result = await this.callHandler(this.app[ns], handler, result)
|
|
909
907
|
await this.runHook('bajo:afterReadConfig', file, result, options)
|
|
908
|
+
if (cache.name) await this.app.cache.save(cache.name, result, cache.ttlDur)
|
|
910
909
|
return result
|
|
911
910
|
}
|
|
912
911
|
|
|
912
|
+
let result
|
|
913
|
+
if (cache.name) result = await this.app.cache.load(cache.name, cache.ttlDur)
|
|
914
|
+
if (result) return result
|
|
913
915
|
await this.runHook('bajo:beforeReadConfig', file, options)
|
|
914
916
|
parserOpts.readFromFile = true
|
|
915
917
|
if (!ns) ns = this.ns
|
|
@@ -999,19 +1001,19 @@ class Bajo extends Plugin {
|
|
|
999
1001
|
* @param {string} path - Base path to start looking config files
|
|
1000
1002
|
* @returns {Object}
|
|
1001
1003
|
*/
|
|
1002
|
-
readAllConfigs = async (path) => {
|
|
1004
|
+
readAllConfigs = async (path, options) => {
|
|
1003
1005
|
const { defaultsDeep } = this.app.lib.aneka
|
|
1004
1006
|
let cfg = {}
|
|
1005
1007
|
let ext = {}
|
|
1006
1008
|
// default config file
|
|
1007
1009
|
try {
|
|
1008
|
-
cfg = await this.readConfig(`${path}
|
|
1010
|
+
cfg = await this.readConfig(`${path}.*`, options)
|
|
1009
1011
|
} catch (err) {
|
|
1010
1012
|
if (['BAJO_CONFIG_NO_PARSER'].includes(err.code)) throw err
|
|
1011
1013
|
}
|
|
1012
1014
|
// env based config file
|
|
1013
1015
|
try {
|
|
1014
|
-
ext = await this.readConfig(`${path}-${this.config.env}
|
|
1016
|
+
ext = await this.readConfig(`${path}-${this.config.env}.*`, options)
|
|
1015
1017
|
} catch (err) {
|
|
1016
1018
|
if (!['BAJO_CONFIG_FILE_NOT_FOUND'].includes(err.code)) throw err
|
|
1017
1019
|
}
|
|
@@ -1028,15 +1030,7 @@ class Bajo extends Plugin {
|
|
|
1028
1030
|
* @returns {Array} Array of hook execution results
|
|
1029
1031
|
*/
|
|
1030
1032
|
runHook = async (hookName, ...args) => {
|
|
1031
|
-
let
|
|
1032
|
-
let path
|
|
1033
|
-
let subNs
|
|
1034
|
-
try {
|
|
1035
|
-
({ ns, subNs, path } = this.breakNsPath(hookName ?? ''))
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
return
|
|
1038
|
-
}
|
|
1039
|
-
let fns = filter(this.hooks, { ns, subNs, path })
|
|
1033
|
+
let fns = filter(this.hooks, { name: hookName })
|
|
1040
1034
|
if (isEmpty(fns)) return []
|
|
1041
1035
|
fns = orderBy(fns, ['level'])
|
|
1042
1036
|
const results = []
|
package/class/helper/bajo.js
CHANGED
|
@@ -69,7 +69,11 @@ const defConfig = {
|
|
|
69
69
|
id: 'metric'
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
|
-
exitHandler: true
|
|
72
|
+
exitHandler: true,
|
|
73
|
+
cache: {
|
|
74
|
+
purge: [],
|
|
75
|
+
purgeIntvDur: '5m'
|
|
76
|
+
}
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const defMain = `async function factory (pkgName) {
|
|
@@ -106,7 +110,7 @@ export default factory
|
|
|
106
110
|
export async function buildBaseConfig () {
|
|
107
111
|
// dirs
|
|
108
112
|
const { defaultsDeep, textToArray, currentLoc, resolvePath } = this.app.lib.aneka
|
|
109
|
-
this.config = defaultsDeep({}, this.app.
|
|
113
|
+
this.config = defaultsDeep({}, this.app.argv._, this.app.envVars._)
|
|
110
114
|
set(this, 'dir.base', this.app.dir)
|
|
111
115
|
const path = currentLoc(import.meta).dir + '/../..'
|
|
112
116
|
set(this, 'dir.pkg', resolvePath(path))
|
|
@@ -203,9 +207,9 @@ export async function collectConfigHandlers () {
|
|
|
203
207
|
*/
|
|
204
208
|
export async function buildExtConfig () {
|
|
205
209
|
// config merging
|
|
206
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
210
|
+
const { defaultsDeep, includes } = this.app.lib.aneka
|
|
207
211
|
const { parseObject, omitDeep } = this.app.lib
|
|
208
|
-
const { isEmpty, get } = this.app.lib._
|
|
212
|
+
const { isEmpty, get, isString, without } = this.app.lib._
|
|
209
213
|
|
|
210
214
|
let resp = get(this, `app.options.config.${this.ns}`, {})
|
|
211
215
|
if (isEmpty(resp)) resp = await this.readAllConfigs(`${this.dir.data}/config/${this.ns}`)
|
|
@@ -231,6 +235,16 @@ export async function buildExtConfig () {
|
|
|
231
235
|
this.config.exitHandler = false
|
|
232
236
|
}
|
|
233
237
|
if (this.config.runtime.noWarning) process.removeAllListeners('warning')
|
|
238
|
+
if (isString(this.config.cache.purge)) this.config.cache.purge = [this.config.cache.purge]
|
|
239
|
+
this.config.cache.purge = without(this.config.cache.purge, '', null, undefined)
|
|
240
|
+
if (this.config.cache.purge.length > 0) {
|
|
241
|
+
if (includes(['all', '*'], this.config.cache.purge)) this.app.cache.purge('*')
|
|
242
|
+
else {
|
|
243
|
+
for (const name of this.config.cache.purge) {
|
|
244
|
+
this.app.cache.purge(name)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
234
248
|
this.app.log = new Log(this.app)
|
|
235
249
|
this.log.trace('dataDir%s', this.dir.data)
|
|
236
250
|
this.log.debug('configHandlers%s', this.join(exts))
|
package/class/helper/base.js
CHANGED
|
@@ -100,26 +100,43 @@ export async function checkDependencies () {
|
|
|
100
100
|
*/
|
|
101
101
|
export async function collectHooks () {
|
|
102
102
|
const { eachPlugins, runHook, isLogInRange, importModule } = this.bajo
|
|
103
|
-
const
|
|
103
|
+
const { isArray, isPlainObject } = this.lib._
|
|
104
|
+
const me = this // "this" is "app"
|
|
104
105
|
me.bajo.log.trace('collecting%s', this.t('hooks'))
|
|
105
|
-
// collects
|
|
106
106
|
await eachPlugins(async function ({ dir, file }) {
|
|
107
|
-
|
|
108
|
-
const [names, path] = _file.split('@')
|
|
109
|
-
const [ns, subNs] = names.split('.').map(n => camelCase(n))
|
|
110
|
-
const mod = await importModule(file, { asHandler: true })
|
|
107
|
+
let mod = await importModule(file, { asHandler: true })
|
|
111
108
|
if (!mod) return undefined
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
if (file.includes('hook.js')) mod = await mod.handler.call(this)
|
|
110
|
+
if (isArray(mod)) {
|
|
111
|
+
for (const m of mod) {
|
|
112
|
+
if (!isPlainObject(m)) continue
|
|
113
|
+
if (!m.name) throw me.bajo.error('missing%s%s', 'name', file)
|
|
114
|
+
if (isArray(m.name)) {
|
|
115
|
+
for (const name of m.name) {
|
|
116
|
+
me.bajo.hooks.push(merge({}, m, { name, src: this.ns }))
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
m.src = this.ns
|
|
120
|
+
me.bajo.hooks.push(m)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
const _file = file.replace(dir + '/hook/', '').replace('.js', '')
|
|
125
|
+
let [names, path] = _file.split('@')
|
|
126
|
+
names = names.split('$').map(n => trim(n))
|
|
127
|
+
for (let name of names) {
|
|
128
|
+
name = name.split('.').map(n => camelCase(n)).join('.')
|
|
129
|
+
const m = merge({}, mod, { name: `${name}:${camelCase(path)}`, src: this.ns })
|
|
130
|
+
me.bajo.hooks.push(m)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}, { glob: ['hook/*.js', 'hook.js'], prefix: me.bajo.ns })
|
|
115
134
|
// for log trace purpose only
|
|
116
135
|
if (isLogInRange('trace')) {
|
|
117
|
-
const items = groupBy(me.bajo.hooks, item => item.
|
|
136
|
+
const items = groupBy(me.bajo.hooks, item => item.name)
|
|
118
137
|
forOwn(items, (v, k) => {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
|
|
122
|
-
})
|
|
138
|
+
const [name, path] = k.split(':')
|
|
139
|
+
me.bajo.log.trace('- %s:%s (%d)', name, path, v.length)
|
|
123
140
|
})
|
|
124
141
|
}
|
|
125
142
|
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-28
|
|
4
|
+
|
|
5
|
+
- [2.15.0] Add built-in cache feature through ```app.cache``` instance
|
|
6
|
+
- [2.15.0] Bug fix in ```eachPlugins()```
|
|
7
|
+
- [2.15.0] Add cache feature to ```readConfig()```
|
|
8
|
+
- [2.15.0] Add feature to read many hooks as one file
|
|
9
|
+
|
|
10
|
+
## 2026-05-22
|
|
11
|
+
|
|
12
|
+
- [2.14.1] Bug fix in ```helper.buildBaseConfig()```
|
|
13
|
+
- [2.14.1] Bug fix in ```helper.collectHooks()```
|
|
14
|
+
|
|
3
15
|
## 2026-05-16
|
|
4
16
|
|
|
5
17
|
- [2.14.0] Add ```bajo:beforeReadConfig()``` hook
|