bajo 1.2.9 → 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 (166) 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} +448 -35
  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/{boot/lib → lib}/log-levels.js +16 -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 -13
  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 -86
  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 -96
  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/test/method/isSet.js +0 -43
  156. /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
  157. /package/{bajo → extend/bajo}/intl/id.json +0 -0
  158. /package/{waibuStatic → extend/waibuStatic}/virtual.json +0 -0
  159. /package/{boot/index.js → index.js} +0 -0
  160. /package/{boot/lib → lib}/current-loc.js +0 -0
  161. /package/{boot/lib → lib}/dayjs.js +0 -0
  162. /package/{boot/lib → lib}/import-module.js +0 -0
  163. /package/{boot/lib → lib}/omitted-plugin-keys.js +0 -0
  164. /package/{boot/lib → lib}/parse-env.js +0 -0
  165. /package/{boot/lib → lib}/read-all-configs.js +0 -0
  166. /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,21 +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
93
  this.envs = { dev: 'development', staging: 'staging', prod: 'production' }
94
+ this.lib.Plugin = Plugin
47
95
  }
48
96
 
49
97
  async _defConfigHandler (file, opts = {}) {
@@ -52,13 +100,29 @@ class BajoCore extends Plugin {
52
100
  return mod
53
101
  }
54
102
 
55
- resolvePath = (item, asFileUrl) => {
56
- 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)
57
114
  }
58
115
 
59
- freeze = (o, shallow) => {
60
- if (shallow) Object.freeze(o)
61
- 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)
62
126
  }
63
127
 
64
128
  setImmediate = async () => {
@@ -93,14 +157,33 @@ class BajoCore extends Plugin {
93
157
  return `${ns}:${path}`
94
158
  }
95
159
 
96
- breakNsPath = (item = '', defaultNs = 'bajo', checkNs = true) => {
97
- 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(':')
98
181
  const fullNs = ns
99
182
  let subNs
100
183
  let subSubNs
101
184
  path = path.join(':')
102
185
  if (path.startsWith('//')) {
103
- return { path: item } // for: http:// etc
186
+ return { path: name } // for: http:// etc
104
187
  }
105
188
 
106
189
  [ns, subNs, subSubNs] = ns.split('.')
@@ -138,6 +221,23 @@ class BajoCore extends Plugin {
138
221
  return { ns, path, subNs, subSubNs, qs, fullPath, fullNs, realPath, realFullPath }
139
222
  }
140
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
+ */
141
241
  buildCollections = async (options = {}) => {
142
242
  let { ns, handler, dupChecks = [], container, useDefaultName } = options
143
243
  useDefaultName = useDefaultName ?? true
@@ -172,15 +272,28 @@ class BajoCore extends Plugin {
172
272
  if (checkers.includes(checker)) this.app[ns].fatal('oneOrMoreSharedTheSame%s%s', container, this.join(dupChecks.filter(i => !isFunction(i))))
173
273
  }
174
274
  }
175
- await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container)
275
+ await this.runHook(`${ns}:${camelCase('afterBuildCollection')}`, container, items)
176
276
  this.app[ns].log.debug('collected%s%d', this.app[ns].print.write(container), items.length)
177
277
  return items
178
278
  }
179
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
+ */
180
293
  callHandler = async (item, ...args) => {
181
294
  let result
182
295
  let scope = this
183
- if (item instanceof BajoPlugin) {
296
+ if (item instanceof Plugin) {
184
297
  scope = item
185
298
  item = args.shift()
186
299
  }
@@ -200,12 +313,30 @@ class BajoCore extends Plugin {
200
313
  }
201
314
  } else if (isFunction(item)) {
202
315
  result = await item.call(scope, ...args)
203
- } else if (isPlainObject(item) && item.handler) {
316
+ } else if (isPlainObject(item) && isFunction(item.handler)) {
204
317
  result = await item.handler.call(scope, ...args)
205
318
  }
206
319
  return result
207
320
  }
208
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
+ */
209
340
  eachPlugins = async (handler, options = {}) => {
210
341
  if (typeof options === 'string') options = { glob: options }
211
342
  const result = {}
@@ -214,11 +345,9 @@ class BajoCore extends Plugin {
214
345
  if (useBajo) pluginPkgs.unshift('bajo')
215
346
  for (const pkgName of pluginPkgs) {
216
347
  const ns = camelCase(pkgName)
217
- const config = this.app[ns].config
218
- const alias = this.app[ns].alias
219
348
  let r
220
349
  if (glob) {
221
- 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}`
222
351
  let opts = isString(glob) ? { pattern: [glob] } : glob
223
352
  let pattern = opts.pattern ?? []
224
353
  if (isString(pattern)) pattern = [pattern]
@@ -229,7 +358,7 @@ class BajoCore extends Plugin {
229
358
  const files = await fastGlob(pattern, opts)
230
359
  for (const f of files) {
231
360
  if (path.basename(f)[0] === '_' && noUnderscore) continue
232
- 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 })
233
362
  if (resp === false) break
234
363
  else if (resp === undefined) continue
235
364
  else {
@@ -238,7 +367,7 @@ class BajoCore extends Plugin {
238
367
  }
239
368
  }
240
369
  } else {
241
- 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 })
242
371
  if (r === false) break
243
372
  else if (r === undefined) continue
244
373
  else result[ns] = r
@@ -256,6 +385,23 @@ class BajoCore extends Plugin {
256
385
  return result
257
386
  }
258
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
+ */
259
405
  getUnitFormat = (options = {}) => {
260
406
  const lang = options.lang ?? this.config.lang
261
407
  let unitSys = options.unitSys ?? this.config.intl.unitSys[lang] ?? 'metric'
@@ -263,6 +409,18 @@ class BajoCore extends Plugin {
263
409
  return { unitSys, format: formats[unitSys] }
264
410
  }
265
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
+ */
266
424
  formatByType = (type, value, dataType, options = {}) => {
267
425
  const { defaultsDeep } = this.lib.aneka
268
426
  const { format } = this.getUnitFormat(options)
@@ -277,6 +435,20 @@ class BajoCore extends Plugin {
277
435
  return `${value}${sep}${unit}`
278
436
  }
279
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
+ */
280
452
  format = (value, type, options = {}) => {
281
453
  const { defaultsDeep } = this.lib.aneka
282
454
  const { format } = this.config.intl
@@ -323,6 +495,17 @@ class BajoCore extends Plugin {
323
495
  return value
324
496
  }
325
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
+ */
326
509
  generateId = (options = {}) => {
327
510
  let type
328
511
  if (options === true) options = 'alpha'
@@ -343,6 +526,14 @@ class BajoCore extends Plugin {
343
526
  return type === 'int' ? parseInt(value) : value
344
527
  }
345
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
+ */
346
537
  getGlobalModuleDir = (pkgName, silent = true) => {
347
538
  let nodeModulesDir = process.env.BAJO_GLOBAL_MODULE_DIR
348
539
  if (!nodeModulesDir) {
@@ -363,6 +554,14 @@ class BajoCore extends Plugin {
363
554
  return dir
364
555
  }
365
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
+ */
366
565
  getMethod = (name = '', thrown = true) => {
367
566
  const { ns, path } = this.breakNsPath(name)
368
567
  const method = get(this.app, `${ns}.${path}`)
@@ -370,6 +569,14 @@ class BajoCore extends Plugin {
370
569
  if (thrown) throw this.error('cantFindMethod%s', name)
371
570
  }
372
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
+ */
373
580
  findDeep = (item, paths) => {
374
581
  let dir
375
582
  for (const p of paths) {
@@ -382,6 +589,14 @@ class BajoCore extends Plugin {
382
589
  return dir
383
590
  }
384
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
+ */
385
600
  getModuleDir = (pkgName, base) => {
386
601
  if (pkgName === 'main') return resolvePath(this.app.dir)
387
602
  if (base === 'main') base = this.app.dir
@@ -397,6 +612,14 @@ class BajoCore extends Plugin {
397
612
  return resolvePath(path.dirname(dir))
398
613
  }
399
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
+ */
400
623
  getPluginDataDir = (name, ensureDir = true) => {
401
624
  const plugin = this.getPlugin(name)
402
625
  const dir = `${this.app.bajo.dir.data}/plugins/${plugin.name}`
@@ -404,6 +627,16 @@ class BajoCore extends Plugin {
404
627
  return dir
405
628
  }
406
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
+ */
407
640
  getPluginFile = (file) => {
408
641
  if (!this) return file
409
642
  if (file[0] === '.') file = `${currentLoc(import.meta).dir}/${trim(file.slice(1), '/')}`
@@ -417,13 +650,21 @@ class BajoCore extends Plugin {
417
650
  return file
418
651
  }
419
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
+ */
420
661
  getPlugin = (name, silent) => {
421
662
  if (!this.app[name]) {
422
663
  // alias?
423
664
  let plugin
424
665
  for (const key in this.app) {
425
666
  const item = this.app[key]
426
- if (item instanceof BajoPlugin && (item.alias === name || item.pkgName === name)) {
667
+ if (item instanceof Plugin && (item.alias === name || item.pkgName === name)) {
427
668
  plugin = item
428
669
  break
429
670
  }
@@ -437,10 +678,54 @@ class BajoCore extends Plugin {
437
678
  return this.app[name]
438
679
  }
439
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
+ */
440
707
  importModule = async (file, { asDefaultImport, asHandler, noCache } = {}) => {
441
708
  return await importModule.call(this, file, { asDefaultImport, asHandler, noCache })
442
709
  }
443
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
+ */
444
729
  importPkg = async (...pkgs) => {
445
730
  const { defaultsDeep } = this.lib.aneka
446
731
  const result = {}
@@ -449,7 +734,8 @@ class BajoCore extends Plugin {
449
734
  if (isPlainObject(last(pkgs))) {
450
735
  opts = defaultsDeep(pkgs.pop(), opts)
451
736
  }
452
- for (const pkg of pkgs) {
737
+ for (let pkg of pkgs) {
738
+ if (pkg.indexOf(':') === -1) pkg = `bajo:${pkg}`
453
739
  const { ns, path: name } = this.breakNsPath(pkg)
454
740
  const dir = this.getModuleDir(name, ns)
455
741
  if (!dir) {
@@ -477,11 +763,28 @@ class BajoCore extends Plugin {
477
763
  return values(result)
478
764
  }
479
765
 
480
- 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))
481
777
  await fs.exists(dir)
482
- return await emptyDir(dir)
778
+ return await emptyDir(dir, filterFn)
483
779
  }
484
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
+ */
485
788
  isLogInRange = (level) => {
486
789
  const levels = keys(logLevels)
487
790
  const logLevel = indexOf(levels, this.app.bajo.config.log.level)
@@ -502,11 +805,27 @@ class BajoCore extends Plugin {
502
805
  }
503
806
  }
504
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
+ */
505
816
  isValidApp = (dir, returnPkg) => {
506
817
  if (!dir) dir = this.app.dir
507
818
  return this.isValidAppPlugin(dir, 'app', returnPkg)
508
819
  }
509
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
+ */
510
829
  isValidPlugin = (dir, returnPkg) => {
511
830
  if (!dir) dir = this.app.dir
512
831
  return this.isValidAppPlugin(dir, 'plugin', returnPkg)
@@ -527,22 +846,59 @@ class BajoCore extends Plugin {
527
846
  return array.map(a => (a + '').trim()).join(separator) + ` ${joiner} ${last}`
528
847
  }
529
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
+ */
530
857
  numUnit = (value = '', defUnit = '') => {
531
858
  const num = value.match(/\d+/g)
532
859
  const unit = value.match(/[a-zA-Z]+/g)
533
860
  return `${num[0]}${isEmpty(unit) ? defUnit : unit[0]}`
534
861
  }
535
862
 
536
- parseDur = (val) => {
537
- 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)
538
872
  }
539
873
 
540
- parseDt = (val) => {
541
- const dt = this.lib.dayjs(val)
542
- if (!dt.isValid()) throw this.error('dtUnparsable%s', val)
543
- 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()
544
885
  }
545
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
+ */
546
902
  parseObject = (input, options = {}) => {
547
903
  const { silent = true, parseValue = false, lang, ns } = options
548
904
  const { isSet } = this.lib.aneka
@@ -552,6 +908,14 @@ class BajoCore extends Plugin {
552
908
  return scope.print.write(text, ...args, { lang })
553
909
  }
554
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
+ }
555
919
  let obj = cloneDeep(input)
556
920
  const keys = Object.keys(obj)
557
921
  const mutated = []
@@ -601,13 +965,31 @@ class BajoCore extends Plugin {
601
965
  return result
602
966
  }
603
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
+ */
604
986
  readConfig = async (file, { ns, pattern, globOptions = {}, ignoreError, defValue = {}, opts = {} } = {}) => {
605
987
  if (!ns) ns = this.name
606
988
  file = resolvePath(this.getPluginFile(file))
607
989
  let ext = path.extname(file)
608
990
  const fname = path.dirname(file) + '/' + path.basename(file, ext)
609
991
  ext = ext.toLowerCase()
610
- if (['.mjs', '.js'].includes(ext)) {
992
+ if (ext === '.js') {
611
993
  const { readHandler } = find(this.app.bajo.configHandlers, { ext })
612
994
  return this.parseObject(await readHandler.call(this.app[ns], file, opts))
613
995
  }
@@ -640,6 +1022,14 @@ class BajoCore extends Plugin {
640
1022
  return this.parseObject(config)
641
1023
  }
642
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
+ */
643
1033
  readJson = (file, thrownNotFound = false) => {
644
1034
  if (isPlainObject(thrownNotFound)) thrownNotFound = false
645
1035
  if (!fs.existsSync(file) && thrownNotFound) throw this.error('notFound%s%s', this.print.write('file'), file)
@@ -651,10 +1041,19 @@ class BajoCore extends Plugin {
651
1041
  return this.parseObject(JSON.parse(resp))
652
1042
  }
653
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
+ */
654
1053
  runHook = async (hookName, ...args) => {
655
1054
  const [ns, path] = (hookName ?? '').split(':')
656
1055
  let fns = filter(this.app.bajo.hooks, { ns, path })
657
- if (isEmpty(fns)) return
1056
+ if (isEmpty(fns)) return []
658
1057
  fns = orderBy(fns, ['level'])
659
1058
  const results = []
660
1059
  for (const i in fns) {
@@ -670,15 +1069,29 @@ class BajoCore extends Plugin {
670
1069
  return results
671
1070
  }
672
1071
 
673
- 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) => {
674
1087
  const { print, getPluginDataDir } = this.app.bajo
675
- const fname = increment(`${getPluginDataDir(this.name)}/${trim(file, '/')}`, { fs: true })
1088
+ const fname = increment(`${getPluginDataDir(this.name)}/download/${trim(file, '/')}`, { fs: true })
676
1089
  const dir = path.dirname(fname)
677
1090
  if (!fs.existsSync(dir)) fs.ensureDirSync(dir)
678
- await fs.writeFile(fname, obj, 'utf8')
1091
+ await fs.writeFile(fname, item, 'utf8')
679
1092
  if (printSaved) print.succeed('savedAs%s', path.resolve(fname), { skipSilence: true })
680
1093
  return fname
681
1094
  }
682
1095
  }
683
1096
 
684
- export default BajoCore
1097
+ export default Bajo