bajo 1.2.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/.jsdoc.conf.json +6 -3
  2. package/README.md +8 -148
  3. package/class/app.js +113 -0
  4. package/{boot/class/bajo-core.js → class/bajo.js} +449 -37
  5. package/{boot/class/error.js → class/base/err.js} +29 -3
  6. package/class/base/log.js +205 -0
  7. package/class/base/plugin.js +177 -0
  8. package/class/base/print.js +272 -0
  9. package/class/helper/bajo.js +344 -0
  10. package/class/helper/plugin.js +169 -0
  11. package/{boot/class/bajo-plugin.js → class/plugin.js} +60 -3
  12. package/docs/App.html +3 -0
  13. package/docs/Bajo.html +15 -0
  14. package/docs/BasePlugin.html +5 -0
  15. package/docs/Err.html +3 -0
  16. package/docs/Log.html +3 -0
  17. package/docs/Plugin.html +3 -0
  18. package/docs/Print.html +3 -0
  19. package/docs/bitcoin.jpeg +0 -0
  20. package/docs/class_app.js.html +116 -0
  21. package/docs/class_bajo.js.html +1100 -0
  22. package/docs/class_base_err.js.html +99 -0
  23. package/docs/class_base_log.js.html +208 -0
  24. package/docs/class_base_plugin.js.html +180 -0
  25. package/docs/class_base_print.js.html +275 -0
  26. package/docs/class_helper_bajo.js.html +347 -0
  27. package/docs/class_helper_plugin.js.html +172 -0
  28. package/docs/class_plugin.js.html +121 -0
  29. package/docs/data/search.json +1 -0
  30. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  31. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  32. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  33. package/docs/global.html +3 -0
  34. package/docs/index.html +3 -0
  35. package/docs/lib_create-method.js.html +42 -0
  36. package/docs/lib_formats.js.html +68 -0
  37. package/docs/lib_log-levels.js.html +28 -0
  38. package/docs/lib_resolve-path.js.html +30 -0
  39. package/docs/lib_shim.js.html +35 -0
  40. package/docs/module-class_helper_bajo.html +3 -0
  41. package/docs/module-class_helper_plugin.html +3 -0
  42. package/docs/module-lib_create-method.html +3 -0
  43. package/docs/module-lib_formats.html +3 -0
  44. package/docs/module-lib_log-levels.html +3 -0
  45. package/docs/module-lib_resolve-path.html +3 -0
  46. package/docs/module-lib_shim.html +3 -0
  47. package/docs/scripts/core.js +726 -0
  48. package/docs/scripts/core.min.js +23 -0
  49. package/docs/scripts/resize.js +90 -0
  50. package/docs/scripts/search.js +265 -0
  51. package/docs/scripts/search.min.js +6 -0
  52. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  53. package/docs/scripts/third-party/fuse.js +9 -0
  54. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  55. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  56. package/docs/scripts/third-party/hljs-original.js +5171 -0
  57. package/docs/scripts/third-party/hljs.js +1 -0
  58. package/docs/scripts/third-party/popper.js +5 -0
  59. package/docs/scripts/third-party/tippy.js +1 -0
  60. package/docs/scripts/third-party/tocbot.js +672 -0
  61. package/docs/scripts/third-party/tocbot.min.js +1 -0
  62. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  63. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  64. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  65. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  66. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  67. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  68. package/docs/tutorial-contribution.html +3 -0
  69. package/docs/tutorial-ecosystem.html +3 -0
  70. package/docs/tutorial-getting-started.html +13 -0
  71. package/docs/tutorial-plugin-dev.html +3 -0
  72. package/docs/tutorial-user-guide.html +3 -0
  73. package/lib/create-method.js +39 -0
  74. package/{boot/lib → lib}/formats.js +28 -0
  75. package/lib/log-levels.js +25 -0
  76. package/{boot/lib → lib}/parse-args-argv.js +1 -1
  77. package/{boot/lib → lib}/resolve-path.js +12 -0
  78. package/{boot/lib → lib}/shim.js +10 -0
  79. package/misc-docs/bitcoin.jpeg +0 -0
  80. package/misc-docs/contribution.md +20 -0
  81. package/{docs → misc-docs}/ecosystem.md +41 -12
  82. package/misc-docs/getting-started.md +142 -0
  83. package/misc-docs/plugin-dev.md +0 -0
  84. package/misc-docs/toc.json +17 -0
  85. package/misc-docs/user-guide.md +1 -0
  86. package/package.json +14 -12
  87. package/bajoBook/book/doc/.metadata.json +0 -28
  88. package/bajoBook/book/doc/How-to-Make-a-Paper-Boat-564x400@2x.jpg +0 -0
  89. package/bajoBook/book/doc/pages/guides/definition.md +0 -7
  90. package/bajoBook/book/doc/pages/guides/intro.md +0 -3
  91. package/bajoBook/book/doc/pages/guides/setup.md +0 -22
  92. package/bajoBook/book/reference/.metadata.json +0 -152
  93. package/bajoBook/book/reference/concept-leadership-business-with-paper-boats.jpg +0 -0
  94. package/bajoBook/book/reference/pages/configuration/configuration-file.md +0 -52
  95. package/bajoBook/book/reference/pages/helper/break-ns-path.md +0 -24
  96. package/bajoBook/book/reference/pages/helper/build-collections.md +0 -19
  97. package/bajoBook/book/reference/pages/helper/call-helper-or-handler.md +0 -35
  98. package/bajoBook/book/reference/pages/helper/current-loc.md +0 -28
  99. package/bajoBook/book/reference/pages/helper/dayjs.md +0 -13
  100. package/bajoBook/book/reference/pages/helper/defaults-deep.md +0 -29
  101. package/bajoBook/book/reference/pages/helper/dump.md +0 -32
  102. package/bajoBook/book/reference/pages/helper/each-plugins.md +0 -24
  103. package/bajoBook/book/reference/pages/helper/envs.md +0 -11
  104. package/bajoBook/book/reference/pages/helper/error.md +0 -29
  105. package/bajoBook/book/reference/pages/helper/fatal.md +0 -18
  106. package/bajoBook/book/reference/pages/helper/freeze.md +0 -13
  107. package/bajoBook/book/reference/pages/helper/generate-id.md +0 -36
  108. package/bajoBook/book/reference/pages/helper/get-config.md +0 -27
  109. package/bajoBook/book/reference/pages/helper/get-global-module-dir.md +0 -13
  110. package/bajoBook/book/reference/pages/helper/get-helper.md +0 -13
  111. package/bajoBook/book/reference/pages/helper/get-item-by-name.md +0 -13
  112. package/bajoBook/book/reference/pages/helper/get-key-by-value.md +0 -13
  113. package/bajoBook/book/reference/pages/helper/get-module-dir.md +0 -13
  114. package/bajoBook/book/reference/pages/helper/get-plugin-data-dir.md +0 -13
  115. package/bajoBook/book/reference/pages/helper/get-plugin-name.md +0 -13
  116. package/bajoBook/book/reference/pages/helper/get-plugin.md +0 -13
  117. package/bajoBook/book/reference/pages/helper/import-module.md +0 -13
  118. package/bajoBook/book/reference/pages/helper/import-pkg.md +0 -13
  119. package/bajoBook/book/reference/pages/helper/is-empty-dir.md +0 -13
  120. package/bajoBook/book/reference/pages/helper/is-log-in-range.md +0 -13
  121. package/bajoBook/book/reference/pages/helper/is-set.md +0 -13
  122. package/bajoBook/book/reference/pages/helper/is-valid-app.md +0 -13
  123. package/bajoBook/book/reference/pages/helper/is-valid-plugin.md +0 -13
  124. package/bajoBook/book/reference/pages/helper/log-levels.md +0 -13
  125. package/bajoBook/book/reference/pages/helper/log.md +0 -13
  126. package/bajoBook/book/reference/pages/helper/paginate.md +0 -13
  127. package/bajoBook/book/reference/pages/helper/pascal-case.md +0 -13
  128. package/bajoBook/book/reference/pages/helper/print.md +0 -13
  129. package/bajoBook/book/reference/pages/helper/read-config.md +0 -13
  130. package/bajoBook/book/reference/pages/helper/read-json.md +0 -13
  131. package/bajoBook/book/reference/pages/helper/resolve-path.md +0 -13
  132. package/bajoBook/book/reference/pages/helper/resolve-tpl-path.md +0 -13
  133. package/bajoBook/book/reference/pages/helper/run-hook.md +0 -13
  134. package/bajoBook/book/reference/pages/helper/save-as-download.md +0 -13
  135. package/bajoBook/book/reference/pages/helper/titleize.md +0 -13
  136. package/bajoBook/book/reference/pages/helper/white-space.md +0 -13
  137. package/boot/class/app.js +0 -67
  138. package/boot/class/bajo-core/boot-order.js +0 -35
  139. package/boot/class/bajo-core/boot-plugins.js +0 -17
  140. package/boot/class/bajo-core/build-config.js +0 -85
  141. package/boot/class/bajo-core/build-plugins.js +0 -44
  142. package/boot/class/bajo-core/collect-config-handlers.js +0 -20
  143. package/boot/class/bajo-core/exit-handler.js +0 -53
  144. package/boot/class/bajo-core/run-as-applet.js +0 -26
  145. package/boot/class/bajo-plugin/attach-method.js +0 -14
  146. package/boot/class/bajo-plugin/build-config.js +0 -15
  147. package/boot/class/bajo-plugin/check-clash.js +0 -18
  148. package/boot/class/bajo-plugin/check-dependency.js +0 -39
  149. package/boot/class/bajo-plugin/collect-hooks.js +0 -31
  150. package/boot/class/bajo-plugin/run.js +0 -23
  151. package/boot/class/log.js +0 -90
  152. package/boot/class/plugin.js +0 -79
  153. package/boot/class/print.js +0 -153
  154. package/boot/lib/create-method.js +0 -33
  155. package/boot/lib/log-levels.js +0 -1
  156. package/test/method/isSet.js +0 -43
  157. /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
  158. /package/{bajo → extend/bajo}/intl/id.json +0 -0
  159. /package/{waibuStatic → extend/waibuStatic}/virtual.json +0 -0
  160. /package/{boot/index.js → index.js} +0 -0
  161. /package/{boot/lib → lib}/current-loc.js +0 -0
  162. /package/{boot/lib → lib}/dayjs.js +0 -0
  163. /package/{boot/lib → lib}/import-module.js +0 -0
  164. /package/{boot/lib → lib}/omitted-plugin-keys.js +0 -0
  165. /package/{boot/lib → lib}/parse-env.js +0 -0
  166. /package/{boot/lib → lib}/read-all-configs.js +0 -0
  167. /package/{docs/hook.md → misc-docs/.hook.md} +0 -0
@@ -1,5 +1,5 @@
1
1
  import Plugin from './plugin.js'
2
- import BajoPlugin from './bajo-plugin.js'
2
+ import BasePlugin from './base/plugin.js'
3
3
  import increment from 'add-filename-increment'
4
4
  import fs from 'fs-extra'
5
5
  import path from 'path'
@@ -29,22 +29,69 @@ const {
29
29
  last, get, has, values, dropRight, pick
30
30
  } = lodash
31
31
 
32
- class BajoCore extends Plugin {
32
+ /**
33
+ * The Core. The main engine. The one and only plugin that control app's boot process and
34
+ * making sure all other plugins working smoothly.
35
+ *
36
+ * @class
37
+ */
38
+ class Bajo extends BasePlugin {
39
+ /**
40
+ * @param {Object} app
41
+ * @param {Object} app - App instance reference. Usefull to call app method inside a plugin
42
+ */
33
43
  constructor (app) {
34
44
  super('bajo', app)
45
+ /**
46
+ * Date/time when your app start
47
+ * @type {Date}
48
+ */
35
49
  this.runAt = new Date()
50
+
51
+ /**
52
+ * Your main namespace. And yes, you suppose NOT to change this
53
+ *
54
+ * @type {string}
55
+ */
36
56
  this.mainNs = 'main'
37
- this.lib.BajoPlugin = BajoPlugin
57
+
58
+ /**
59
+ * Storage for applets
60
+ *
61
+ * @type {Array}
62
+ */
38
63
  this.applets = []
64
+
65
+ /**
66
+ * List of all loaded plugin's package names
67
+ *
68
+ * @type {Array}
69
+ */
39
70
  this.pluginPkgs = []
71
+
72
+ /**
73
+ * List of all loaded plugin's names
74
+ *
75
+ * @type {Array}
76
+ */
40
77
  this.pluginNames = []
78
+
79
+ /**
80
+ * Storage for config handlers. By default there are only two handlers available: ```.js```
81
+ * and ```.json```.
82
+ *
83
+ * Use plugin to add more type - e.g: {@link https://github.com/ardhi/bajo-config|bajo-config}
84
+ * lets you to use ```.yaml``` and ```.toml```
85
+ *
86
+ * @type {Array}
87
+ */
41
88
  this.configHandlers = [
42
89
  { ext: '.js', readHandler: this._defConfigHandler },
43
90
  { ext: '.json', readHandler: this.readJson }
44
91
  ]
45
92
  this.whiteSpace = [' ', '\t', '\n', '\r']
46
- this.logLevels = logLevels
47
93
  this.envs = { dev: 'development', staging: 'staging', prod: 'production' }
94
+ this.lib.Plugin = Plugin
48
95
  }
49
96
 
50
97
  async _defConfigHandler (file, opts = {}) {
@@ -53,13 +100,29 @@ class BajoCore extends Plugin {
53
100
  return mod
54
101
  }
55
102
 
56
- resolvePath = (item, asFileUrl) => {
57
- return resolvePath(item, asFileUrl)
103
+ /**
104
+ * Resolve file name to filesystem's path. Windows path separator ```\```
105
+ * is normalized to Unix's ```/```
106
+ *
107
+ * @method
108
+ * @param {string} file - File to resolve
109
+ * @param {boolean} [asFileUrl=false] - Return as file URL format ```file:///<name>```
110
+ * @returns {string}
111
+ */
112
+ resolvePath = (file, asFileUrl) => {
113
+ return resolvePath(file, asFileUrl)
58
114
  }
59
115
 
60
- freeze = (o, shallow) => {
61
- if (shallow) Object.freeze(o)
62
- else deepFreeze(o)
116
+ /**
117
+ * Freeze object
118
+ *
119
+ * @method
120
+ * @param {Object} obj - Object
121
+ * @param {boolean} [shallow=false] - If true (default), deep freeze object
122
+ */
123
+ freeze = (obj, shallow) => {
124
+ if (shallow) Object.freeze(obj)
125
+ else deepFreeze(obj)
63
126
  }
64
127
 
65
128
  setImmediate = async () => {
@@ -94,14 +157,33 @@ class BajoCore extends Plugin {
94
157
  return `${ns}:${path}`
95
158
  }
96
159
 
97
- breakNsPath = (item = '', defaultNs = 'bajo', checkNs = true) => {
98
- let [ns, ...path] = item.split(':')
160
+ /**
161
+ * Object returned by {@link BajoCore#breakNsPath|breakNsPath}
162
+ *
163
+ * @typedef {Object} NsPathType
164
+ * @property {string} ns - Namespace
165
+ * @property {string} [subNs] - Sub namespace
166
+ * @property {string} [subSubNs] - Sub of sub namespace
167
+ * @property {string} path - Path without query string or hash
168
+ * @property {string} fullPath - Full path, including query string and hash
169
+ */
170
+
171
+ /**
172
+ * Break name to its namespace & path infos
173
+ *
174
+ * @method
175
+ * @param {string} name - Name to break
176
+ * @param {boolean} [checkNs=true] - If true (default), namespace will be checked for its validity
177
+ * @returns {NsPathType}
178
+ */
179
+ breakNsPath = (name = '', checkNs = true) => {
180
+ let [ns, ...path] = name.split(':')
99
181
  const fullNs = ns
100
182
  let subNs
101
183
  let subSubNs
102
184
  path = path.join(':')
103
185
  if (path.startsWith('//')) {
104
- return { path: item } // for: http:// etc
186
+ return { path: name } // for: http:// etc
105
187
  }
106
188
 
107
189
  [ns, subNs, subSubNs] = ns.split('.')
@@ -139,6 +221,23 @@ class BajoCore extends Plugin {
139
221
  return { ns, path, subNs, subSubNs, qs, fullPath, fullNs, realPath, realFullPath }
140
222
  }
141
223
 
224
+ /**
225
+ * Method to transform an array or object from config into an array of collection safely.
226
+ *
227
+ * Emitted hooks:
228
+ * 1. ```{ns}:beforeBuildCollection (container)``` - called before collection is built
229
+ * 2. ```{ns}:afterBuildCollection (container, items)``` - called after collection is built
230
+ *
231
+ * @method
232
+ * @async
233
+ * @param {Object} options - Options
234
+ * @param {string} [options.ns] - Namespace. If not provided, defaults to ```bajo```
235
+ * @param {function} [options.handler] - Handler to call while building the collection item
236
+ * @param {Array} [options.dupChecks=[]] - Array of keys to check for duplicates
237
+ * @param {string} options.container - Key used as container name
238
+ * @param {boolean} [options.useDefaultName=true] - If true (default) and ```name``` key is not provided, returned collection will be named ```default```
239
+ * @returns {Array} The collection
240
+ */
142
241
  buildCollections = async (options = {}) => {
143
242
  let { ns, handler, dupChecks = [], container, useDefaultName } = options
144
243
  useDefaultName = useDefaultName ?? true
@@ -173,15 +272,28 @@ class BajoCore extends Plugin {
173
272
  if (checkers.includes(checker)) this.app[ns].fatal('oneOrMoreSharedTheSame%s%s', container, this.join(dupChecks.filter(i => !isFunction(i))))
174
273
  }
175
274
  }
176
- await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container)
275
+ await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container, items)
177
276
  this.app[ns].log.debug('collected%s%d', this.app[ns].print.write(container), items.length)
178
277
  return items
179
278
  }
180
279
 
280
+ /**
281
+ * Calling any plugin's method by its name. Name format: ```ns:methodName```.
282
+ * - If name is a string, the corresponding plugin's method will be called with passed args as its parameters
283
+ * - If name is a plugin instance, this will be used as the scope instead. The first args is now the handler name and the rest are its parameters
284
+ * - If name is a function, this function will be run under scope with the remaining args
285
+ * - If name is an object and has ```handler``` key in it, this function handler will be instead
286
+ *
287
+ * @method
288
+ * @async
289
+ * @param {(string|Object|function)} name - Method's name, function handler, plain object or plugin instance
290
+ * @param {...any} [args] - One or more arguments passed as parameter to the handler
291
+ * @returns {any} Returned value
292
+ */
181
293
  callHandler = async (item, ...args) => {
182
294
  let result
183
295
  let scope = this
184
- if (item instanceof BajoPlugin) {
296
+ if (item instanceof Plugin) {
185
297
  scope = item
186
298
  item = args.shift()
187
299
  }
@@ -201,12 +313,30 @@ class BajoCore extends Plugin {
201
313
  }
202
314
  } else if (isFunction(item)) {
203
315
  result = await item.call(scope, ...args)
204
- } else if (isPlainObject(item) && item.handler) {
316
+ } else if (isPlainObject(item) && isFunction(item.handler)) {
205
317
  result = await item.handler.call(scope, ...args)
206
318
  }
207
319
  return result
208
320
  }
209
321
 
322
+ /**
323
+ * This function iterates through all loaded plugins and call the provided handler scoped as the running plugin.
324
+ * And an object with the following key serves as its parameter:
325
+ *
326
+ * - ```file```: file matched the glob pattern
327
+ * - ```dir```: plugin's base directory
328
+ *
329
+ * @method
330
+ * @async
331
+ * @param {function} handler - Function handler. Can be an async function. Scoped to the running plugin
332
+ * @param {(string|Object)} [options={}] - Options. If a string is provided, it serves as the glob pattern, otherwise:
333
+ * @param {(string|Array)} [options.glob] - Glob pattern. If provided,
334
+ * @param {boolean} [options.useBajo=false] - If true, add ```bajo``` to the running plugins too
335
+ * @param {string} [options.prefix=''] - Prepend glob pattern with prefix
336
+ * @param {boolean} [options.noUnderscore=true] - If true (default), matched file with name starts with underscore is ignored
337
+ * @param {any} [options.returnItems] - If true, each value of returned handler call will be saved as an object with running plugin name as its keys
338
+ * @returns {any}
339
+ */
210
340
  eachPlugins = async (handler, options = {}) => {
211
341
  if (typeof options === 'string') options = { glob: options }
212
342
  const result = {}
@@ -215,11 +345,9 @@ class BajoCore extends Plugin {
215
345
  if (useBajo) pluginPkgs.unshift('bajo')
216
346
  for (const pkgName of pluginPkgs) {
217
347
  const ns = camelCase(pkgName)
218
- const config = this.app[ns].config
219
- const alias = this.app[ns].alias
220
348
  let r
221
349
  if (glob) {
222
- const base = prefix === '' ? this.app[ns].dir.pkg : `${this.app[ns].dir.pkg}/${prefix}`
350
+ const base = prefix === '' ? `${this.app[ns].dir.pkg}/extend` : `${this.app[ns].dir.pkg}/extend/${prefix}`
223
351
  let opts = isString(glob) ? { pattern: [glob] } : glob
224
352
  let pattern = opts.pattern ?? []
225
353
  if (isString(pattern)) pattern = [pattern]
@@ -230,7 +358,7 @@ class BajoCore extends Plugin {
230
358
  const files = await fastGlob(pattern, opts)
231
359
  for (const f of files) {
232
360
  if (path.basename(f)[0] === '_' && noUnderscore) continue
233
- const resp = await handler.call(this.app[ns], { ns, pkgName, config, alias, file: f, dir: base })
361
+ const resp = await handler.call(this.app[ns], { file: f, dir: base })
234
362
  if (resp === false) break
235
363
  else if (resp === undefined) continue
236
364
  else {
@@ -239,7 +367,7 @@ class BajoCore extends Plugin {
239
367
  }
240
368
  }
241
369
  } else {
242
- r = await handler.call(this.app[ns], { ns, pkgName, config, dir: this.app[ns].dir.pkg, alias })
370
+ r = await handler.call(this.app[ns], { dir: this.app[ns].dir.pkg })
243
371
  if (r === false) break
244
372
  else if (r === undefined) continue
245
373
  else result[ns] = r
@@ -257,6 +385,23 @@ class BajoCore extends Plugin {
257
385
  return result
258
386
  }
259
387
 
388
+ /**
389
+ * Object returned by {@link BajoCore#getUnitFormat|getUnitFormat}
390
+ *
391
+ * @typedef {Object} ObjectFormatType
392
+ * @property {string} unitSys - Unit system
393
+ * @property {Object} format - Format object
394
+ */
395
+
396
+ /**
397
+ * Get unit format
398
+ *
399
+ * @method
400
+ * @param {Object} [options={}] - Options
401
+ * @param {string} [options.lang] - Language to use. Defaults to the one you set in config
402
+ * @param {string} [options.unitSys] - Unit system to use. Defaults to language's unit system or ```metric``` if unspecified
403
+ * @returns {ObjectFormatType} - Returned value
404
+ */
260
405
  getUnitFormat = (options = {}) => {
261
406
  const lang = options.lang ?? this.config.lang
262
407
  let unitSys = options.unitSys ?? this.config.intl.unitSys[lang] ?? 'metric'
@@ -264,6 +409,18 @@ class BajoCore extends Plugin {
264
409
  return { unitSys, format: formats[unitSys] }
265
410
  }
266
411
 
412
+ /**
413
+ * Format value by type
414
+ *
415
+ * @method
416
+ * @param {string} type - Format type. See {@link FormatType} for acceptable values
417
+ * @param {any} value - Value to format
418
+ * @param {string} [dataType] - Value's data type. See {@link DataType} for acceptable values
419
+ * @param {Object} [options={}] - Options
420
+ * @param {boolean} [options.withUnit=true] - Return with its unit appended
421
+ * @param {string} [options.lang] - Format value according to this language. Defaults to the one you set in config
422
+ * @returns {(Array|string)} Return string if ```withUnit``` is true. Otherwise is an array of ```[value, unit, separator]```
423
+ */
267
424
  formatByType = (type, value, dataType, options = {}) => {
268
425
  const { defaultsDeep } = this.lib.aneka
269
426
  const { format } = this.getUnitFormat(options)
@@ -278,6 +435,20 @@ class BajoCore extends Plugin {
278
435
  return `${value}${sep}${unit}`
279
436
  }
280
437
 
438
+ /**
439
+ * Format value
440
+ *
441
+ * @method
442
+ * @param {any} value - Value to format
443
+ * @param {string} [type] - Data type to use. See {@link DataType} for acceptable values. If not provided, return the untouched value
444
+ * @param {Object} [options={}] - Options
445
+ * @param {string} [options.emptyValue=''] - Empty value to use if function resulted empty. Defaults to the one from your config
446
+ * @param {boolean} [options.withUnit=true] - Return with its unit appended
447
+ * @param {string} [options.lang] - Format value according to this language. Defaults to the one you set in config
448
+ * @param {string} [options.latitude] - If Bajo Spatial is loaded and data type is a double or float, then format it as latitude in degree, minute, second
449
+ * @param {string} [options.longitude] - If Bajo Spatial is loaded and data type is a double or float, then format it as longitude in degree, minute, second
450
+ * @returns {string} Formatted value
451
+ */
281
452
  format = (value, type, options = {}) => {
282
453
  const { defaultsDeep } = this.lib.aneka
283
454
  const { format } = this.config.intl
@@ -324,6 +495,17 @@ class BajoCore extends Plugin {
324
495
  return value
325
496
  }
326
497
 
498
+ /**
499
+ * Generate unique random characters that can be used as ID. Use {@link https://github.com/ai/nanoid|nanoid} under the hood
500
+ *
501
+ * @method
502
+ * @param {(boolean|string|Object)} [options={}] - Options. If set to ```true``` or ```alpha```, it will generate alphaphet only characters. If set to ```int```, it will generate integer only characters. Otherwise:
503
+ * @param {string} [options.pattern] - Character pattern to use. Defaults to all available alphanumeric characters
504
+ * @param {number} [options.length=13] - Length of resulted characters
505
+ * @param {string} [options.case] - If set to ```lower``` to use lower cased pattern only. For upper cased pattern, set it to ```upper```
506
+ * @param {boolean} [options.returnInstance] - Set to ```true``` to return {@link https://github.com/ai/nanoid|nanoid} instance instead of string
507
+ * @returns {(string|Object)} Return string or instance of {@link https://github.com/ai/nanoid|nanoid}
508
+ */
327
509
  generateId = (options = {}) => {
328
510
  let type
329
511
  if (options === true) options = 'alpha'
@@ -344,6 +526,14 @@ class BajoCore extends Plugin {
344
526
  return type === 'int' ? parseInt(value) : value
345
527
  }
346
528
 
529
+ /**
530
+ * Get NPM global module directory
531
+ *
532
+ * @method
533
+ * @param {string} [pkgName] - If provided, return this package global directory. Otherwise the npm global module directory
534
+ * @param {boolean} [silent=true] - Set to ```false``` to throw exception in case of error. Otherwise silently returns undefined
535
+ * @returns {string}
536
+ */
347
537
  getGlobalModuleDir = (pkgName, silent = true) => {
348
538
  let nodeModulesDir = process.env.BAJO_GLOBAL_MODULE_DIR
349
539
  if (!nodeModulesDir) {
@@ -364,6 +554,14 @@ class BajoCore extends Plugin {
364
554
  return dir
365
555
  }
366
556
 
557
+ /**
558
+ * Get class method by name
559
+ *
560
+ * @method
561
+ * @param {string} name - Name in format ```ns:methodName```
562
+ * @param {boolean} [thrown=true] - If ```true``` (default), throw exceptiom in case of error
563
+ * @returns {function} Class method
564
+ */
367
565
  getMethod = (name = '', thrown = true) => {
368
566
  const { ns, path } = this.breakNsPath(name)
369
567
  const method = get(this.app, `${ns}.${path}`)
@@ -371,6 +569,14 @@ class BajoCore extends Plugin {
371
569
  if (thrown) throw this.error('cantFindMethod%s', name)
372
570
  }
373
571
 
572
+ /**
573
+ * Find item deep in paths
574
+ *
575
+ * @method
576
+ * @param {string} item - Item to find
577
+ * @param {Array} paths - Array of path to look for
578
+ * @returns {string}
579
+ */
374
580
  findDeep = (item, paths) => {
375
581
  let dir
376
582
  for (const p of paths) {
@@ -383,6 +589,14 @@ class BajoCore extends Plugin {
383
589
  return dir
384
590
  }
385
591
 
592
+ /**
593
+ * Get module directory, locally and globally
594
+ *
595
+ * @method
596
+ * @param {string} pkgName - Package name to find
597
+ * @param {*} base - Provide base name if ```pkgName``` is a module under ```base```'s package name
598
+ * @returns {string} Return absolute package directory
599
+ */
386
600
  getModuleDir = (pkgName, base) => {
387
601
  if (pkgName === 'main') return resolvePath(this.app.dir)
388
602
  if (base === 'main') base = this.app.dir
@@ -398,6 +612,14 @@ class BajoCore extends Plugin {
398
612
  return resolvePath(path.dirname(dir))
399
613
  }
400
614
 
615
+ /**
616
+ * Get plugin data directory
617
+ *
618
+ * @method
619
+ * @param {string} name - Plugin name (namespace) or alias
620
+ * @param {boolean} [ensureDir=true] - Set ```true``` (default) to ensure directory is existed
621
+ * @returns {string}
622
+ */
401
623
  getPluginDataDir = (name, ensureDir = true) => {
402
624
  const plugin = this.getPlugin(name)
403
625
  const dir = `${this.app.bajo.dir.data}/plugins/${plugin.name}`
@@ -405,6 +627,16 @@ class BajoCore extends Plugin {
405
627
  return dir
406
628
  }
407
629
 
630
+ /**
631
+ * Resolve file path from:
632
+ *
633
+ * - local/absolute file
634
+ * - ns based path (```myPlugin:/path/to/file.txt```)
635
+ *
636
+ * @method
637
+ * @param {string} file - File path, see above for supported types
638
+ * @returns {string} Resolved file path
639
+ */
408
640
  getPluginFile = (file) => {
409
641
  if (!this) return file
410
642
  if (file[0] === '.') file = `${currentLoc(import.meta).dir}/${trim(file.slice(1), '/')}`
@@ -418,13 +650,21 @@ class BajoCore extends Plugin {
418
650
  return file
419
651
  }
420
652
 
653
+ /**
654
+ * Get plugin by name
655
+ *
656
+ * @method
657
+ * @param {string} name - Plugin name/namespace or alias
658
+ * @param {boolean} [silent] - If ```true```, silently return undefined even on error
659
+ * @returns {Object} Plugin object
660
+ */
421
661
  getPlugin = (name, silent) => {
422
662
  if (!this.app[name]) {
423
663
  // alias?
424
664
  let plugin
425
665
  for (const key in this.app) {
426
666
  const item = this.app[key]
427
- if (item instanceof BajoPlugin && (item.alias === name || item.pkgName === name)) {
667
+ if (item instanceof Plugin && (item.alias === name || item.pkgName === name)) {
428
668
  plugin = item
429
669
  break
430
670
  }
@@ -438,10 +678,54 @@ class BajoCore extends Plugin {
438
678
  return this.app[name]
439
679
  }
440
680
 
681
+ /**
682
+ * Import file/module from any loaded plugins
683
+ *
684
+ * Your plugin structure:
685
+ * ```
686
+ * |- src
687
+ * | |- lib
688
+ * | | |- my-module.js
689
+ * |- index.js
690
+ * |- package.json
691
+ * ```
692
+ *
693
+ * Inside your app/plugin:
694
+ * ```javascript
695
+ * const { importModule } = this.app.bajo
696
+ * const myModule = await importModule('myPlugin:/src/lib/my-module.js')
697
+ * ```
698
+ * @method
699
+ * @async
700
+ * @param {string} file - File in format ```ns:<ns based file path>```
701
+ * @param {Object} [options={}] - Options
702
+ * @param {boolean} [options.asDefaultImport=true] - If ```true``` (default), return default imported module
703
+ * @param {boolean} [options.asHandler] - If ```true```, return as a {@link HandlerType|handler}
704
+ * @param {boolean} [options.noCache] - If ```true```, always import as a fresh copy
705
+ * @returns {(function|Object)}
706
+ */
441
707
  importModule = async (file, { asDefaultImport, asHandler, noCache } = {}) => {
442
708
  return await importModule.call(this, file, { asDefaultImport, asHandler, noCache })
443
709
  }
444
710
 
711
+ /**
712
+ * Import one or more package belongs to a plugin
713
+ *
714
+ * Example: you want to import packages ```delay``` and ```chalk``` from ```bajo``` namespace and use it inside your app/plugin
715
+ *
716
+ * ```javascript
717
+ * const { importPkg } from this.app.bajo
718
+ * const [delay, chalk] = await importPkg('bajo:delay', 'bajo:chalk')
719
+ *
720
+ * await delay(1000)
721
+ * ...
722
+ * ```
723
+ *
724
+ * @method
725
+ * @async
726
+ * @param {...any} pkgs - One or more packages in format ```ns:packageName```
727
+ * @returns {(Object|Array)} Depends on how many parameters are provided, it should return the named package or an array of packages
728
+ */
445
729
  importPkg = async (...pkgs) => {
446
730
  const { defaultsDeep } = this.lib.aneka
447
731
  const result = {}
@@ -450,7 +734,8 @@ class BajoCore extends Plugin {
450
734
  if (isPlainObject(last(pkgs))) {
451
735
  opts = defaultsDeep(pkgs.pop(), opts)
452
736
  }
453
- for (const pkg of pkgs) {
737
+ for (let pkg of pkgs) {
738
+ if (pkg.indexOf(':') === -1) pkg = `bajo:${pkg}`
454
739
  const { ns, path: name } = this.breakNsPath(pkg)
455
740
  const dir = this.getModuleDir(name, ns)
456
741
  if (!dir) {
@@ -478,13 +763,30 @@ class BajoCore extends Plugin {
478
763
  return values(result)
479
764
  }
480
765
 
481
- isEmptyDir = async (dir) => {
766
+ /**
767
+ * Check whether directory is empty or not. More info please {@link https://github.com/gulpjs/empty-dir|check here}.
768
+ *
769
+ * @method
770
+ * @async
771
+ * @param {string} dir - Directory to check. Can be a ns based directory too!
772
+ * @param {function} filterFn - Filter function to filter out files that cause false positives.
773
+ * @returns {boolean}
774
+ */
775
+ isEmptyDir = async (dir, filterFn) => {
776
+ dir = resolvePath(this.getPluginFile(dir))
482
777
  await fs.exists(dir)
483
- return await emptyDir(dir)
778
+ return await emptyDir(dir, filterFn)
484
779
  }
485
780
 
781
+ /**
782
+ * Check whether log level is within log's app current level
783
+ *
784
+ * @method
785
+ * @param {string} level - Level to check. See {@link LogLevelsType} for more
786
+ * @returns {boolean}
787
+ */
486
788
  isLogInRange = (level) => {
487
- const levels = keys(this.logLevels)
789
+ const levels = keys(logLevels)
488
790
  const logLevel = indexOf(levels, this.app.bajo.config.log.level)
489
791
  return indexOf(levels, level) >= logLevel
490
792
  }
@@ -503,11 +805,27 @@ class BajoCore extends Plugin {
503
805
  }
504
806
  }
505
807
 
808
+ /**
809
+ * Check whether directory is a valid Bajo app
810
+ *
811
+ * @method
812
+ * @param {string} dir - Directory to check
813
+ * @param {boolean} [returnPkg] - Set ```true``` to return its package.json content
814
+ * @returns {(boolean|Object)}
815
+ */
506
816
  isValidApp = (dir, returnPkg) => {
507
817
  if (!dir) dir = this.app.dir
508
818
  return this.isValidAppPlugin(dir, 'app', returnPkg)
509
819
  }
510
820
 
821
+ /**
822
+ * Check whether directory is a valid Bajo plugin
823
+ *
824
+ * @method
825
+ * @param {string} dir - Directory to check
826
+ * @param {boolean} [returnPkg] - Set ```true``` to return its package.json content
827
+ * @returns {(boolean|Object)}
828
+ */
511
829
  isValidPlugin = (dir, returnPkg) => {
512
830
  if (!dir) dir = this.app.dir
513
831
  return this.isValidAppPlugin(dir, 'plugin', returnPkg)
@@ -528,22 +846,59 @@ class BajoCore extends Plugin {
528
846
  return array.map(a => (a + '').trim()).join(separator) + ` ${joiner} ${last}`
529
847
  }
530
848
 
849
+ /**
850
+ * Return its numeric portion of a value
851
+ *
852
+ * @method
853
+ * @param {string} [value=''] - Value to get its numeric portion
854
+ * @param {string} [defUnit=''] - Default unit if value doesn't have one
855
+ * @returns {string}
856
+ */
531
857
  numUnit = (value = '', defUnit = '') => {
532
858
  const num = value.match(/\d+/g)
533
859
  const unit = value.match(/[a-zA-Z]+/g)
534
860
  return `${num[0]}${isEmpty(unit) ? defUnit : unit[0]}`
535
861
  }
536
862
 
537
- parseDur = (val) => {
538
- return isNumber(val) ? val : ms(val)
863
+ /**
864
+ * Parse duration to its millisecond value. Use {@link https://github.com/vercel/ms|ms} under the hood
865
+ *
866
+ * @method
867
+ * @param {(number|string)} dur - If string is given, parse this to its millisecond value. Otherwise return as is
868
+ * @returns {number}
869
+ */
870
+ parseDur = (dur) => {
871
+ return isNumber(dur) ? dur : ms(dur)
539
872
  }
540
873
 
541
- parseDt = (val) => {
542
- const dt = this.lib.dayjs(val)
543
- if (!dt.isValid()) throw this.error('dtUnparsable%s', val)
544
- return dt.toDate()
874
+ /**
875
+ * Parse datetime string as Javascript object. Please visit {@link https://day.js.org|dayjs} for valid formats and more infos
876
+ *
877
+ * @method
878
+ * @param {string} dt - Datetime string
879
+ * @returns {Object} Javascript object
880
+ */
881
+ parseDt = (dt) => {
882
+ const value = this.lib.dayjs(dt)
883
+ if (!value.isValid()) throw this.error('dtUnparsable%s', dt)
884
+ return value.toDate()
545
885
  }
546
886
 
887
+ /**
888
+ * Parse an object and optionally normalize its values recursively. Use {@link https://github.com/ladjs/dotenv-parse-variables}
889
+ * to parse values, so please have a visit to know how it works
890
+ *
891
+ * If ```options.parseValue``` is ```true```, any key ends with ```Dur``` and ```Dt``` will
892
+ * also be parsed as millisecond and Javascript datetime accordingly
893
+ *
894
+ * @method
895
+ * @param {(Object|string)} input - If string is given, parse it first using JSON.parse
896
+ * @param {Object} [options={}] - Options
897
+ * @param {boolean} [options.silent=true] - If ```true``` (default), exception are not thrown and silently ignored
898
+ * @param {boolean} [options.parseValue=false] - If ```true```, values will be parsed & normalized
899
+ * @param {string} [options.lang] - If provided, use this language instead of the one in config
900
+ * @returns {Object}
901
+ */
547
902
  parseObject = (input, options = {}) => {
548
903
  const { silent = true, parseValue = false, lang, ns } = options
549
904
  const { isSet } = this.lib.aneka
@@ -553,6 +908,14 @@ class BajoCore extends Plugin {
553
908
  return scope.print.write(text, ...args, { lang })
554
909
  }
555
910
  const statics = ['*']
911
+ if (isString(input)) {
912
+ try {
913
+ input = JSON.parse(input)
914
+ } catch (err) {
915
+ if (silent) input = {}
916
+ else throw err
917
+ }
918
+ }
556
919
  let obj = cloneDeep(input)
557
920
  const keys = Object.keys(obj)
558
921
  const mutated = []
@@ -602,13 +965,31 @@ class BajoCore extends Plugin {
602
965
  return result
603
966
  }
604
967
 
968
+ /**
969
+ * Read and parse file as config object. Supported types: ```.js``` and ```.json```.
970
+ * More supports can be added using plugin. {@link https://github.com/ardhi/bajo-config|bajo-config} gives you additional supports for ```.yml```, ```.yaml``` and ```.toml``` file
971
+ *
972
+ * If file extension is ```.*```, it will be auto detected and parsed accordingly
973
+ *
974
+ * @method
975
+ * @async
976
+ * @param {string} file - File to read and parse
977
+ * @param {Object} [options={}] - Options
978
+ * @param {boolean} [options.ignoreError] - Any exception will be silently discarded
979
+ * @param {string} [options.ns] - If given, use this as the scope
980
+ * @param {string} [options.pattern] - If given and auto detection is on (extension is ```.*```), it will be used for instead the default one
981
+ * @param {Object} [options.globOptions={}] - {@link https://github.com/mrmlnc/fast-glob|fast-glob} options
982
+ * @param {Object} [options.defValue={}] - Default value to use if value returned empty
983
+ * @param {Object} [options.opts={}] - Parser setting
984
+ * @returns {Object}
985
+ */
605
986
  readConfig = async (file, { ns, pattern, globOptions = {}, ignoreError, defValue = {}, opts = {} } = {}) => {
606
987
  if (!ns) ns = this.name
607
988
  file = resolvePath(this.getPluginFile(file))
608
989
  let ext = path.extname(file)
609
990
  const fname = path.dirname(file) + '/' + path.basename(file, ext)
610
991
  ext = ext.toLowerCase()
611
- if (['.mjs', '.js'].includes(ext)) {
992
+ if (ext === '.js') {
612
993
  const { readHandler } = find(this.app.bajo.configHandlers, { ext })
613
994
  return this.parseObject(await readHandler.call(this.app[ns], file, opts))
614
995
  }
@@ -641,6 +1022,14 @@ class BajoCore extends Plugin {
641
1022
  return this.parseObject(config)
642
1023
  }
643
1024
 
1025
+ /**
1026
+ * Read and parse json file
1027
+ *
1028
+ * @method
1029
+ * @param {string} file - File to read
1030
+ * @param {boolean} [thrownNotFound=false] - If ```true```, silently ignore if file is not found
1031
+ * @returns {Object}
1032
+ */
644
1033
  readJson = (file, thrownNotFound = false) => {
645
1034
  if (isPlainObject(thrownNotFound)) thrownNotFound = false
646
1035
  if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.print.write('file'), file)
@@ -652,10 +1041,19 @@ class BajoCore extends Plugin {
652
1041
  return this.parseObject(JSON.parse(resp))
653
1042
  }
654
1043
 
1044
+ /**
1045
+ * Run named hook
1046
+ *
1047
+ * @method
1048
+ * @async
1049
+ * @param {string} hookName - ns based hook name
1050
+ * @param {...any} [args] - Argument passed to the hook function
1051
+ * @returns {Array} Array of hook execution results
1052
+ */
655
1053
  runHook = async (hookName, ...args) => {
656
1054
  const [ns, path] = (hookName ?? '').split(':')
657
1055
  let fns = filter(this.app.bajo.hooks, { ns, path })
658
- if (isEmpty(fns)) return
1056
+ if (isEmpty(fns)) return []
659
1057
  fns = orderBy(fns, ['level'])
660
1058
  const results = []
661
1059
  for (const i in fns) {
@@ -671,15 +1069,29 @@ class BajoCore extends Plugin {
671
1069
  return results
672
1070
  }
673
1071
 
674
- saveAsDownload = async (file, obj, printSaved = true) => {
1072
+ /**
1073
+ * Save item as file in Bajo's download directory. That is a directory inside your
1074
+ * Bajo plugin's data directory.
1075
+ *
1076
+ * If file exists already, file will automatically be
1077
+ * renamed incrementally.
1078
+ *
1079
+ * @method
1080
+ * @async
1081
+ * @param {string} file - File name
1082
+ * @param {Object} item - Item to save
1083
+ * @param {boolean} [printSaved=true] - Print info on screen
1084
+ * @returns {string} Full file path
1085
+ */
1086
+ saveAsDownload = async (file, item, printSaved = true) => {
675
1087
  const { print, getPluginDataDir } = this.app.bajo
676
- const fname = increment(`${getPluginDataDir(this.name)}/${trim(file, '/')}`, { fs: true })
1088
+ const fname = increment(`${getPluginDataDir(this.name)}/download/${trim(file, '/')}`, { fs: true })
677
1089
  const dir = path.dirname(fname)
678
1090
  if (!fs.existsSync(dir)) fs.ensureDirSync(dir)
679
- await fs.writeFile(fname, obj, 'utf8')
1091
+ await fs.writeFile(fname, item, 'utf8')
680
1092
  if (printSaved) print.succeed('savedAs%s', path.resolve(fname), { skipSilence: true })
681
1093
  return fname
682
1094
  }
683
1095
  }
684
1096
 
685
- export default BajoCore
1097
+ export default Bajo