bajo 2.23.0 → 2.24.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 (108) hide show
  1. package/.jsdoc.conf.json +2 -1
  2. package/class/app.js +220 -107
  3. package/class/bajo.js +246 -174
  4. package/class/base.js +85 -24
  5. package/class/cache.js +8 -16
  6. package/class/err.js +31 -10
  7. package/class/log.js +46 -38
  8. package/class/plugin.js +53 -30
  9. package/class/print.js +18 -15
  10. package/class/tools.js +6 -5
  11. package/docs/App.html +51 -1
  12. package/docs/Bajo.html +1 -1
  13. package/docs/Base.html +34 -1
  14. package/docs/Cache.html +1 -1
  15. package/docs/Err.html +2 -2
  16. package/docs/Log.html +2 -2
  17. package/docs/Main.html +3 -0
  18. package/docs/Plugin.html +1 -1
  19. package/docs/Print.html +1 -1
  20. package/docs/Tools.html +1 -1
  21. package/docs/class_app.js.html +222 -109
  22. package/docs/class_bajo.js.html +260 -179
  23. package/docs/class_base.js.html +87 -26
  24. package/docs/class_cache.js.html +10 -18
  25. package/docs/class_err.js.html +33 -12
  26. package/docs/class_log.js.html +48 -40
  27. package/docs/class_plugin.js.html +55 -32
  28. package/docs/class_print.js.html +20 -17
  29. package/docs/class_tools.js.html +8 -7
  30. package/docs/data/search.json +1 -1
  31. package/docs/extend_bajo_hook.js.html +289 -0
  32. package/docs/global.html +22 -1
  33. package/docs/index.html +1 -1
  34. package/docs/index.js.html +53 -16
  35. package/docs/lib_applet-hook.js.html +328 -0
  36. package/docs/lib_config.js.html +167 -0
  37. package/docs/lib_helper.js.html +186 -192
  38. package/docs/module-Applet.html +3 -0
  39. package/docs/module-Helper-Main.html +3 -0
  40. package/docs/module-Helper.html +1 -8
  41. package/docs/module-Hook.html +1 -1
  42. package/docs/tutorial-01_WELCOME.html +3 -0
  43. package/docs/tutorial-02_GETTING_STARTED.html +125 -0
  44. package/docs/tutorial-03_USER_GUIDE.html +54 -0
  45. package/docs/tutorial-04_DEV_GUIDE.html +178 -0
  46. package/docs/tutorial-05_ECOSYSTEM.html +3 -0
  47. package/extend/bajo/intl/en-US.json +2 -1
  48. package/extend/bajo/intl/id.json +2 -1
  49. package/index.js +51 -14
  50. package/lib/applet-hook.js +325 -0
  51. package/lib/config.js +164 -0
  52. package/lib/helper.js +184 -190
  53. package/package.json +6 -2
  54. package/test/00-global-hooks.test.js +44 -0
  55. package/test/e2e/_run.js +19 -0
  56. package/test/e2e/e2e-applet-process.test.js +66 -0
  57. package/test/e2e/e2e-boot-process.test.js +62 -0
  58. package/test/e2e/e2e-hooks-process.test.js +68 -0
  59. package/test/integration/_setup.js +55 -0
  60. package/test/integration/aspect-01-boot-instance.test.js +24 -0
  61. package/test/integration/aspect-02-plugin-loading.test.js +24 -0
  62. package/test/integration/aspect-03-config-merge.test.js +27 -0
  63. package/test/integration/aspect-04-intl-loading.test.js +29 -0
  64. package/test/integration/aspect-05-cache-roundtrip.test.js +26 -0
  65. package/test/integration/aspect-06-log-file-save.test.js +28 -0
  66. package/test/integration/aspect-07-plugin-data-dir.test.js +25 -0
  67. package/test/integration/aspect-08-plugin-lookup.test.js +25 -0
  68. package/test/integration/aspect-09-config-formats.test.js +26 -0
  69. package/test/integration/aspect-10-hooks.test.js +38 -0
  70. package/test/unit/app.test.js +115 -0
  71. package/test/unit/applet-hook.test.js +10 -0
  72. package/test/unit/bajo.test.js +151 -0
  73. package/test/unit/base.test.js +90 -0
  74. package/test/unit/cache.test.js +89 -0
  75. package/test/unit/config.test.js +14 -0
  76. package/test/unit/err.test.js +57 -0
  77. package/test/unit/helper.test.js +116 -0
  78. package/test/unit/index.test.js +40 -0
  79. package/test/unit/log.test.js +84 -0
  80. package/test/unit/plugin.test.js +95 -0
  81. package/test/unit/print.test.js +72 -0
  82. package/test/unit/tools.test.js +29 -0
  83. package/wiki/CHANGES.md +4 -0
  84. package/wiki/tutorials/01_WELCOME.md +18 -0
  85. package/wiki/tutorials/02_GETTING_STARTED.md +366 -0
  86. package/wiki/tutorials/03_USER_GUIDE.md +262 -0
  87. package/wiki/tutorials/04_DEV_GUIDE.md +386 -0
  88. package/wiki/tutorials/05_ECOSYSTEM.md +95 -0
  89. package/wiki/tutorials/tutorials.json +17 -0
  90. package/lib/hook.js +0 -228
  91. package/test/app.test.js +0 -183
  92. package/test/bajo.test.js +0 -124
  93. package/test/base.test.js +0 -75
  94. package/test/cache.test.js +0 -94
  95. package/test/e2e.test.js +0 -137
  96. package/test/err.test.js +0 -73
  97. package/test/helper.test.js +0 -39
  98. package/test/import-module.test.js +0 -138
  99. package/test/integration.test.js +0 -218
  100. package/test/log.test.js +0 -119
  101. package/test/plugin.test.js +0 -116
  102. package/test/print.test.js +0 -100
  103. package/test/tools.test.js +0 -38
  104. package/wiki/CONFIG.md +0 -36
  105. package/wiki/DEV_GUIDE.md +0 -3
  106. package/wiki/ECOSYSTEM.md +0 -93
  107. package/wiki/GETTING-STARTED.md +0 -356
  108. package/wiki/USER-GUIDE.md +0 -256
@@ -0,0 +1,366 @@
1
+ # Getting Started
2
+
3
+ Welcome! In this guide, we will take our first steps with Bajo together.
4
+
5
+ If this is your first time working with this framework, that is completely fine. We will move step by step: installation, project structure, basic configuration, and your first small app. Along the way, you will also meet two important ideas in Bajo: plugins and hook/lifecycle.
6
+
7
+ By the end of this tutorial, you should feel comfortable enough to continue to Bajo's sub-frameworks, such as Dobo and Waibu.
8
+
9
+ ## Installation
10
+
11
+ First, create a new empty directory named ```my-project```. We will use this as the app directory throughout the tutorial. Then, move into that directory with ```cd``` and run:
12
+
13
+ ```bash
14
+ $ npm init
15
+ ```
16
+
17
+ You will be asked for information such as project name, description, and author. Continue until the *package.json* file is created.
18
+
19
+ Next, open that file in your editor and add the following lines:
20
+
21
+ ```javascript
22
+ ...
23
+ "type": "module",
24
+ "bajo": {
25
+ "type": "app"
26
+ },
27
+ ...
28
+ ```
29
+
30
+ After that, install Bajo:
31
+
32
+ ```bash
33
+ $ npm install bajo
34
+ ```
35
+
36
+ Now create a bootstrap file named `index.js` and add:
37
+
38
+ ```javascript
39
+ // index.js file
40
+ import { boot } from 'bajo'
41
+ await boot()
42
+ ```
43
+
44
+ A Bajo app **always** needs a data directory for configuration files and related resources. This directory can be inside or outside your app directory.
45
+
46
+ If the directory does not exist yet, Bajo will ask you whether to create one automatically or to abort. If you decide to create it, Bajo will make a directory named `data`, next to your `index.js` file. By default, this becomes your data directory.
47
+
48
+ Now run your app:
49
+
50
+ ```bash
51
+ $ node index.js
52
+ ```
53
+
54
+ Great work. Your first Bajo app is now running.
55
+
56
+ ## Playing Around
57
+
58
+ At this point, your project structure should look like this:
59
+
60
+ ```
61
+ .
62
+ └── my-project
63
+ ├── data
64
+ │ └── config
65
+ ├── main
66
+ │ └── index.js
67
+ ├── node_modules
68
+ │ └── ...
69
+ ├── index.js
70
+ ├── package.json
71
+ └── package-lock.json
72
+ ```
73
+
74
+ Bajo automatically creates a `main` plugin for you. This is the main plugin of your app, and it is where you will put your code.
75
+
76
+ By default, the app runs in the `dev` environment. In this mode, the log level is set to `debug`. You can override it using program arguments to something like `trace`, which is the most verbose level, and some other options:
77
+
78
+ ```bash
79
+ $ node index.js --log-level=trace --log-timeTaken --log-pretty
80
+ ```
81
+
82
+ You should now see more colorful logs, including timing information for each step. This is very useful for debugging and for finding slow parts of the startup process.
83
+
84
+ Typing arguments every time can feel repetitive, so let's move those settings into a config file. Create `data/config/bajo.yml` and add:
85
+
86
+ ```yaml
87
+ env: dev
88
+ log:
89
+ pretty: true
90
+ level: trace
91
+ timeTaken: true
92
+ ```
93
+
94
+ > If you like JSON, you can also use `bajo.json` instead of `bajo.yml`. Bajo supports both formats.
95
+
96
+ Now try running the app again without extra arguments:
97
+
98
+ ```bash
99
+ $ node index.js
100
+ ```
101
+
102
+ Much easier.
103
+
104
+ You can combine config values, program arguments, environment variables, and even use a dotenv file (`.env`) whenever needed. For a deeper explanation, please see the *User Guide*.
105
+
106
+ ## Your First Project
107
+
108
+ Now let's build a simple **Hello World** example, Bajo style.
109
+
110
+ Our goals are:
111
+ 1. Read values from configuration.
112
+ 2. Copy those values into plugin properties during initialization.
113
+ 3. Display the values while the app is running.
114
+ 4. Print a message when the app exits.
115
+
116
+ Let's begin.
117
+
118
+ ### Config Object
119
+
120
+ Open `data/config/main.yml` (or create it if it does not exist). This is the configuration file for the main plugin.
121
+
122
+ Add:
123
+
124
+ ```yaml
125
+ firstName: Tanjiro
126
+ lastName: Kamado
127
+ age: 15
128
+ ```
129
+
130
+ Each Bajo plugin can read its configuration from `{dataDir}/config/{ns}.yml`, where `{dataDir}` is your data directory and `{ns}` is the plugin namespace or name. You can read more in the {@tutorial 03_USER_GUIDE}.
131
+
132
+ In Bajo, most features are built through plugins. For small projects, using the built-in `main` plugin is usually enough. As your project grows, you can split responsibilities into smaller independent plugins.
133
+
134
+ If you want to build your own plugin, please continue with the {@tutorial 04_DEV_GUIDE}.
135
+
136
+ ### Plugin Factory
137
+
138
+ Now open `main/index.js` and update it like this:
139
+
140
+ This file is your main plugin factory. Bajo creates it automatically when needed.
141
+
142
+ ```javascript
143
+ async function factory (pkgName) {
144
+ const me = this
145
+
146
+ return class Main extends this.app.baseClass.Base {
147
+ constructor () {
148
+ super(pkgName, me.app)
149
+ this.config = {}
150
+ }
151
+
152
+ // start adding these lines
153
+ init = async () => {
154
+ this.firstName = this.config.firstName
155
+ this.lastName = this.config.lastName
156
+ this.age = this.config.age
157
+ }
158
+
159
+ start = async () => {
160
+ this.log.info('First name: %s, Last name: %s, age: %d', this.firstName, this.lastName, this.age)
161
+ }
162
+
163
+ exit = async () => {
164
+ this.log.warn('Program aborted')
165
+ }
166
+ // end of added lines
167
+ }
168
+ }
169
+
170
+ export default factory
171
+ ```
172
+
173
+ Here is what happens during the lifecycle:
174
+
175
+ - During boot, the main plugin reads `main.yml` and merges it with matching program arguments and environment variables to build the plugin's configuration object.
176
+ - Next, initialization runs through main plugin `init` method. In this example, we copy values from `this.config` into plugin properties.
177
+ - Then the plugin starts through the `start` method, where we print the values to the logger.
178
+ - Finally, when the app exits, the `exit` method runs.
179
+
180
+ This is the standard lifecycle pattern for all Bajo plugins, including the special main plugin.
181
+
182
+ But if you run it now, you will likely get an error. The reason is that keys from the config file (`firstName`, `lastName`, and `age`) are not yet declared in `this.config`, so they are ignored during initialization.
183
+
184
+ Let's fix that by defining default values first:
185
+
186
+ ```javascript
187
+ ...
188
+ constructor () {
189
+ super(pkgName, me.app)
190
+ this.config = {
191
+ firstName: 'John',
192
+ lastName: 'Doe',
193
+ age: 50
194
+ }
195
+ }
196
+ ...
197
+ ```
198
+
199
+ The `this.config` object in the constructor acts as the default configuration. During startup, Bajo merges it with values from configuration files or program options. If any key is missing in the file, the default value is used.
200
+
201
+ Run the app again, and this time let's keep output concise with:
202
+
203
+ ```bash
204
+ $ node index.js --log-level=info
205
+ 2025-09-12T00:09:38.946Z +97ms INFO: main First name: Tanjiro, Last name: Kamado, age: 15
206
+ 2025-09-12T00:09:38.949Z +3ms WARN: main Program aborted
207
+ ```
208
+
209
+ Nice!
210
+
211
+ ## The Hook System
212
+
213
+ ### Tapping a Hook
214
+
215
+ Bajo has a very powerful, yet so simple hook system so you can add custom behavior at specific moments. We will start with a simple case: running code right after boot completes.
216
+
217
+ Create `main/extend/bajo/hook/bajo@after-boot.js`. If the naming looks unusual, that is expected; please read {@link module:Hook} file naming instructions for more.
218
+
219
+ ```javascript
220
+ async function afterBootComplete () {
221
+ this.log.info('Hook after boot complete')
222
+ }
223
+
224
+ export default afterBootComplete
225
+ ```
226
+
227
+ That is your first hook listener. It taps into the `bajo:afterBoot` hook, which runs after the boot process completes. You can check the full list of hooks {@link module:Hook|here}.
228
+
229
+ ### Your Own Hook
230
+
231
+ Now let's write your own custom hook. For this small example it may feel too over the top, but it shows how flexible Bajo can be.
232
+
233
+ This time, we will update all properties through a hook.
234
+
235
+ Open `index.js` and update it like this:
236
+
237
+ ```javascript
238
+ ...
239
+ init = async () => {
240
+ const { runHook } = this.app.bajo // add this line
241
+ this.firstName = this.config.firstName
242
+ this.lastName = this.config.lastName
243
+ this.age = this.config.age
244
+ await runHook('main:myHook') // and this line
245
+ }
246
+ ...
247
+ ```
248
+
249
+ This will add a hook named `main:myHook` during app boot.
250
+
251
+ Now create a new file as the hook listener for the hook you just created: `main/extend/bajo/hook/main@my-hook.js`
252
+
253
+ ```javascript
254
+ async function myHook () {
255
+ // 'this' is bound to the owner plugin, that is the main plugin in this case
256
+ this.lastName = 'THE Daemon Slayer'
257
+ }
258
+
259
+ export default myHook
260
+ ```
261
+
262
+ In Bajo, class methods, hooks, and handlers run within its plugin scope, so setting `this.lastName` directly inside the hook is enough.
263
+
264
+ Run the app again. You should see output similar to this:
265
+
266
+ ```bash
267
+ 2025-09-12T12:09:09.004Z +115ms INFO: main First name: Tanjiro, Last name: THE Daemon Slayer, age: 15
268
+ 2025-09-12T12:09:09.008Z +4ms INFO: main Hook after boot complete
269
+ 2025-09-12T12:09:09.009Z +1ms WARN: main Program aborted
270
+ ```
271
+
272
+ As you can see, `this.lastName` was updated by the hook successfully. You can also see the `afterBoot` hook running after the main plugin starts.
273
+
274
+ ## Using External Plugins
275
+
276
+ Bajo is designed as an ecosystem of small plugins. You can think of them like building blocks: combine the ones you need to create your own app behavior.
277
+
278
+ In this section, we will use plugins to extend the app.
279
+
280
+ ### TOML File Format
281
+
282
+ Let's switch the configuration format to TOML.
283
+
284
+ 1. TOML support is provided by the `bajo-config` plugin, so install it first:
285
+ ```bash
286
+ $ npm install bajo-config
287
+ ```
288
+ 2. Open `data/config/.plugins` and add `bajo-config` in it. If the file does not exist yet, create it first. Plugin order is usually resolved automatically by Bajo.
289
+ 3. Remove `data/config/main.yml` and create `data/config/main.toml`.
290
+ 4. Add the same object as before, now in TOML:
291
+ ```
292
+ firstName = "Tanjiro"
293
+ lastName = "Kamado"
294
+ age = 15
295
+ ```
296
+ 5. Run the app and check the result. It should match the previous behavior, except for different timestamps.
297
+
298
+ ### Applet Mode
299
+
300
+ **Applets** are small tools embedded within plugins that run when Bajo is in **applet mode**. They have their own lifecycle, separate from the main program, but they can still reuse shared resources and config.
301
+
302
+ To run Bajo in applet mode, use `--applet` or `-a`:
303
+
304
+ ```bash
305
+ $ node index.js -a
306
+ ```
307
+
308
+ Applet mode needs `bajo-cli`, so install it first:
309
+
310
+ ```bash
311
+ $ npm install bajo-cli
312
+ ```
313
+
314
+ Then add `bajo-cli` to the `data/config/.plugins` file. Again, plugin order is not a concern in this case.
315
+
316
+ If you run it now, your terminal may show something like this:
317
+
318
+ ```bash
319
+ ℹ App runs in applet mode
320
+ ? Please select: (Use arrow keys)
321
+ ❯ bajoConfig
322
+ bajoCli
323
+ ```
324
+
325
+ Notice that the app reports it is running in applet mode. The interface becomes an interactive CLI view, and regular logs are hidden.
326
+
327
+ By default, logs are disabled in applet mode to keep the console clean. During debugging, you can enable logs again by adding `--log-applet` to your command.
328
+
329
+ Applets are optional utilities provided by plugin authors, so it could be the case that some plugins have many applets in it while others have none.
330
+
331
+ ### System Info
332
+
333
+ Now let's install one more plugin: `bajo-sysinfo`. It is a thin wrapper over [systeminformation](https://github.com/sebhildebrandt/systeminformation) with a few helpful additions:
334
+
335
+ - It can be called directly as an applet.
336
+ - It is also exposed as Waibu REST API endpoints. We will cover it much later.
337
+
338
+ If you run the app like this (yes, `--applet` can take a value; details are {@link module:Applet|here}, you may see output like the following after loading:
339
+
340
+ ```bash
341
+ $ node index.js -a bajoSysinfo:battery
342
+ ℹ App runs in applet mode
343
+ ℹ Done!
344
+ ┌──────────────────┬─────────────────────┐
345
+ │ hasBattery │ true │
346
+ ├──────────────────┼─────────────────────┤
347
+ │ cycleCount │ 0 │
348
+ ├──────────────────┼─────────────────────┤
349
+ │ isCharging │ true │
350
+ ├──────────────────┼─────────────────────┤
351
+ │ designedCapacity │ 61998 │
352
+ ├──────────────────┼─────────────────────┤
353
+ │ maxCapacity │ 51686 │
354
+ ├──────────────────┼─────────────────────┤
355
+ ...
356
+ ```
357
+
358
+ ## More
359
+
360
+ Bajo offers much more beyond this tiny introduction. When you are ready, continue with these sub-framework guides below. In
361
+ the mean time, please feel free to also explore the {@tutorial 03_USER_GUIDE} and {@tutorial 04_DEV_GUIDE}.
362
+
363
+ - [Dobo DBMS](https://github.com/ardhi/dobo/wiki/GETTING-STARTED.md)
364
+ - [Waibu Web Framework](https://github.com/ardhi/waibu/wiki/GETTING-STARTED.md)
365
+ - [Sumba Biz Suites](https://github.com/ardhi/sumba/wiki/GETTING-STARTED.md)
366
+ - [Masohi Messaging](https://github.com/ardhi/masohi/wiki/GETTING-STARTED.md)
@@ -0,0 +1,262 @@
1
+ # User Guide
2
+
3
+ ## Overview
4
+
5
+ ### Definitions
6
+
7
+ Before we go any further, let's define some terms that will be used throughout this guide:
8
+
9
+ 1. `{appDir}` - The application directory, which is the root directory of your Bajo app. This is where all your application files reside, and it is typically the directory where you run your app from.
10
+ 2. `{dataDir}` - The data directory, which is where Bajo stores its configuration files, plugin data, and other persistent information. By default, this is a subdirectory named `data` within your `{appDir}`.
11
+ 3. `{tmpDir}` - The temporary directory, which is where Bajo stores temporary files and caches. By default, Bajo will simply use the system's temporary directory, but you can configure it to be a directory of your choice.
12
+ 4. `{pkgName}` - The plugin's package name, that is the name in `package.json`, typically matching its npm listing.
13
+ 5. `{ns}`: The plugin namespace or name for short, which is the camel-cased version of the package name.
14
+ 6. `{mainNs}` - The special plugin's `main` namespace and directory named `main` within your `{appDir}`; this is where all application code should be written. It is created automatically if it doesn't exist yet.
15
+
16
+ ### Directory Structure
17
+
18
+ Your typical Bajo app directory structure should look like this:
19
+
20
+ ```
21
+ .
22
+ └── {appDir}
23
+ ├── {dataDir}
24
+ │ ├── config
25
+ │ │ ├── .plugin
26
+ │ │ ├── bajo.yml
27
+ │ │ ├── main.yml
28
+ │ │ └── ...
29
+ │ └── plugins
30
+ │ └── ...
31
+ ├── main
32
+ │ ├── extend
33
+ │ │ └── ...
34
+ │ ├── index.js
35
+ │ └── ...
36
+ ├── package.json
37
+ ├── index.js
38
+ └── ...
39
+ ```
40
+
41
+ This to note:
42
+ 1. You can move `{dataDir}` out of `{appDir}` if you want, but you need to tell Bajo where to find it. For more on this, please follow along.
43
+ 2. `{dataDir}` should be the only place Bajo **writes** anything. Bajo and its plugins should **never** be allowed to write anything outside `{dataDir}` on their own.
44
+ 3. `config` is a special directory within `{dataDir}` where your configuration files should reside. Inside this directory, you should find:
45
+ - a special file named `.plugins` that tells Bajo which plugins should be loaded
46
+ - a file named `bajo.yml` to override global settings
47
+ - all plugin-specific config files, named after their namespace
48
+ - you can use `.json` if you prefer, but `.yml` is recommended for better readability
49
+ 4. The `main` directory, or `{mainNs}` namespace, is the {@link Main|special plugin} where you put your application code. And yes, it is actually a normal Bajo plugin! This means everything in there will be handled just like a regular plugin like it has the ability to extend other plugins, has its own config file, and more — with a few differences:
50
+ - it's always available and can't be disabled
51
+ - it's always the last one to start
52
+ - if this directory is missing, it will be created automatically on startup
53
+ - if the plugin's factory function is missing (`index.js`), it will be created dynamically
54
+ - {@link Main|click here} for more details about the `main` plugin
55
+ 5. `index.js` is the main entry point for your app.
56
+
57
+ To set your `{dataDir}` somewhere else, you need to tell Bajo where to find it by using an argument switch.
58
+
59
+ Assuming your data directory is `my-data-dir` at the same level as your app directory, run your app like this:
60
+
61
+ ```bash
62
+ $ node index.js --dir-data=../my-data-dir
63
+ ```
64
+
65
+ If using program arguments seems a bit like a hassle for you, just use Bajo's [dotenv](https://github.com/motdotla/dotenv) support. Create a `.env` file in your app directory and put this inside:
66
+
67
+ ```text
68
+ DIR__DATA=../my-data-dir # double underscores!!!
69
+ ```
70
+
71
+ From now on, you can start the app just by typing:
72
+
73
+ ```bash
74
+ $ node index.js
75
+ ```
76
+
77
+ ### Runtime
78
+
79
+ Bajo should run perfectly fine on Node.js version 20 or higher. Using the latest stable runtime is recommended. Bajo-based apps are also known to run with **Bun** without any problems. But Bajo **cannot** run on Deno due to its heavy reliance on Node.js-specific libraries and environments.
80
+
81
+ Bajo is a pure ES6 framework that utilizes dynamic imports `a lot`. Running on a system with a fast disk (e.g., SSD) and enough RAM is highly recommended, especially when you load a lot of plugins.
82
+
83
+ ## Configuration
84
+
85
+ ### General Rules
86
+
87
+ - All configuration files must be placed in the `{dataDir}/config` subfolder.
88
+ - Config files must be named after their plugin namespace.
89
+ - The file format should be either `.yml`, `.json` or `.js`. If a `.js` file is used, it must be in ES6 format and should export either a plain JavaScript object or a function. Both synchronous and asynchronous functions are supported. If it returns a function, this function will be called within its plugin scope and should return a plain JS object.
90
+ - Other formats e.g. `.toml` can also be used by installing and loading the [bajoConfig](https://ardhi.github.io/bajo-config) plugin.
91
+ - The order of precedence is `.js` > `.json` > `.yml` > `.toml`. This means that if a `.js` file exists, it will be used instead of a `.json` file or any other format.
92
+
93
+ Example: bajo.yml
94
+ ```yaml
95
+ env: prod
96
+ log:
97
+ pretty: true
98
+ timeTaken: true
99
+ lang: id
100
+ ```
101
+
102
+ ### Using Plugins
103
+
104
+ Plugins are what make the Bajo Framework so great and flexible: they extend app features and functionalities!
105
+
106
+ To use plugins, follow these steps:
107
+
108
+ 1. Install it with `npm install {pkgName}`, where `{pkgName}` is the plugin's package name. You can install as many plugins as you want; for a complete list of plugins, please {@tutorial 05_ECOSYSTEM}.
109
+ 2. Optionally, create `{dataDir}/config/{ns}.yml` to customize the plugin's settings, where `{ns}` is the plugin namespace/name.
110
+ 3. Open or create `{dataDir}/config/.plugins` and list the plugin's `{pkgName}` name in it, one per line. Use a `#` hash mark in front of the package name to disable it. The order of the plugins in this file is mostly NOT important, as Bajo will detect automatically.
111
+
112
+ For example, the text below will load `bajo-config`, `bajo-extra`, and `bajo-template` plugins:
113
+
114
+ ```text
115
+ # .plugin file
116
+ bajo-config
117
+ bajo-extra
118
+ bajo-template
119
+ ```
120
+
121
+ If you later decide to disable one or more plugins, you just need to remove them from the `.plugins` file or place a `#` hash mark in front of the package name and restart your app.
122
+
123
+ > **Warning**: Please do not confuse `{pkgName}` and `{ns}`. The plugin package name is the name of the JS package listed on npm, while `{ns}` is the plugin namespace or name for short, which is basically the camel-cased version of the plugin's package name.
124
+
125
+ ### Environment Support
126
+
127
+ Configuration file support for different environments is also available. All you need to do is create a `{ns}-{env}.yml` file in your `{dataDir}/config`, where:
128
+
129
+ - `{ns}`: the namespace/plugin name
130
+ - `{env}`: your desired environment (`dev` or `prod`)
131
+
132
+ App-wide settings with `bajo-{env}.yml` are also possible.
133
+
134
+ Bajo is smart enough to select which config file will be used based on the following order of precedence:
135
+
136
+ 1. Use `{ns}-{env}.yml` if the file exists.
137
+ 2. If not, use `{ns}.yml`.
138
+ 3. If that also doesn't exist, then use the plugin's default config values.
139
+
140
+ ### Runtime Override
141
+
142
+ #### Environment Variables
143
+
144
+ You can easily override ANY key-value pair setting with environment variables and program argument switches. Bajo also supports [dotenv](https://github.com/motdotla/dotenv) with a `.env` file.
145
+
146
+ The order of precedence is always: environment variable > argument switches > config files > default, built-in values.
147
+
148
+ All values (whether they come from environment variables, argument switches, or config files) will be parsed using [dotenv-parse-variables](https://github.com/ladjs/dotenv-parse-variables), so please make sure you visit the repository to fully understand how it works.
149
+
150
+ - Create or open `{appDir}/.env`
151
+ - Use `__` (double underscores) as replacement for dots in an object.
152
+ - `DIR__DATA`: Sets the `{dataDir}` data directory.
153
+ - `DIR__TMP`: Sets `{tmpDir}` temporary directory.
154
+ - For every key in `{ns|bajo}.yml`, use its snake-cased, upper-cased version, e.g.:
155
+ - `env: 'prod'` → `ENV=prod`
156
+ - `log.dateFormat: 'YYYY-MM-DD'` → `LOG__DATE_FORMAT=YYYY-MM-DD`
157
+ - `exitHandler: true` → `EXIT_HANDLER=true`
158
+
159
+ To override a plugin's config, prepend every key in the plugin's config with the snake-cased, upper-cased version of the namespace followed by a dot. For example:
160
+ - `key` in `myPlugin` → `MY_PLUGIN.KEY=...`
161
+ - `key.subKey.subSubKey` in `myPlugin` → `MY_PLUGIN.KEY__SUB_KEY__SUB_SUB_KEY=...`
162
+
163
+ Example:
164
+ ```text
165
+ # .env file
166
+ ENV=prod
167
+ LOG__PRETTY=true
168
+ LOG__TIME_TAKEN=true
169
+ LANG=id
170
+ ```
171
+
172
+ #### Argument Switches
173
+
174
+ You can also override ANY key-value pair setting with argument switches. This is especially useful when you want to run your app with different settings without changing the `.env` file or config files:
175
+
176
+ - Use switches, e.g.: `node index.js --xxx=one --yyy=two`
177
+ - Use `-` as the replacement for dots in an object.
178
+ - `--dir-data`: Sets the `{dataDir}` data directory.
179
+ - `--dir-tmp`: Sets the `{tmpDir}` temporary directory.
180
+ - For every key in `{ns|bajo}.yml`, add `--` prefix. E.g.:
181
+ - `env: 'prod'` → `--env=prod`
182
+ - `log.dateFormat: 'YYYY-MM-DD'` → `--log-dateFormat=YYYY-MM-DD`
183
+ - `exitHandler: true` → `--exitHandler`
184
+ - To override a plugin's config, prepend every key in the plugin's config with the plugin name followed by a colon `:`. E.g.:
185
+ - `key` in `myPlugin` → `--myPlugin:key=...`
186
+ - `key.subKey.subSubKey` in `myPlugin` → `--myPlugin:key-subKey-subSubKey=...`
187
+
188
+ Example:
189
+ ```bash
190
+ $ node index.js --env=prod --log-pretty --log-timeTaken --lang=id
191
+ ```
192
+
193
+ ## System Hook
194
+
195
+ A **hook** refers to a mechanism that allows you to inject a custom function to extend Bajo's functionality at specific points. These points are typically predefined by the framework, providing opportunities to execute code before, or after a particular operation.
196
+
197
+ ### Usage
198
+
199
+ In Bajo, hooks can be created anywhere very easily. Simply call the `runHook` method followed by a number of parameters you want to pass or none at all. The hook name is always in the form of {@link Bajo.TNsPathPairs}, and represents the hook point any plugin can hook into.
200
+
201
+ You can then create a hook listener in your `main` plugin hook directory, which will be called when the hook is executed.
202
+
203
+ For more info, please read the {@link module:Hook|here}.
204
+
205
+ Example:
206
+
207
+ 1. Somewhere in your JavaScript file, add the following code snippet:
208
+
209
+ ```javascript
210
+ const { runHook } = this.app.bajo
211
+ await runHook('main:sayHello', 'Don', 'Meri', { movie: 'Jumbo', year: 2025 })
212
+ ```
213
+ 2. Go to directory `{appDir}/main/extend/bajo/hook`. Create one if it doesn't exist yet.
214
+ 3. Create file `main@say-hello.js` in the directory above.
215
+ 4. Enter these lines:
216
+ ```javascript
217
+ async function sayHello (...params) {
218
+ const [mainChar, friend, payload] = params
219
+ console.log(mainChar, friend, payload) // output: Don, Meri, { movie: 'Jumbo', year: 2025 }
220
+ }
221
+
222
+ export default sayHello
223
+ ```
224
+
225
+ Note the hook name and its associated file name:
226
+
227
+ `main:sayHello` → `main@say-hello.js`
228
+
229
+ This is because a colon (`:`) is prohibited in a file name, so Bajo replaces it with the `@` symbol. And yes, the file name is case-sensitive and must match a correct hook name, so make sure you use the exact same name in both places. But
230
+ you are also allowed to use the kebab-cased version of the hook name (in fact this is the recommended way to make your file name more readable).
231
+
232
+ During the boot process, Bajo will scan for hook files and load them into the app hook list. When your `runHook` is executed, Bajo will find its related hook from the list. If such a hook exists, its function handler will be called.
233
+
234
+ ### Anatomy
235
+
236
+ Many times, there are more than one handler listening for a particular hook name. Especially in a framework that uses plugins extensively like Bajo, many plugins can listen to one hook at the same time. This creates a problem with call order.
237
+
238
+ To overcome this problem, Bajo gives you the opportunity to set a `level`. Function handlers with a lower level will be called earlier. Functions with no level will be assigned level `999` by default.
239
+
240
+ Now, change your ```main@say-hello.js``` file above to export an object instead of a function:
241
+
242
+ ```javascript
243
+ const sayHello = {
244
+ level: 10, // <-- will get called earlier than any other handler with a higher level
245
+ handler: async function (...params) {
246
+ const [mainChar, friend, payload] = params
247
+ console.log(mainChar, friend, payload) // output: Don, Meri, { movie: 'Jumbo', year: 2025 }
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### Caveats
253
+
254
+ Hooks give you a lot of flexibility and freedom, but you need to be aware of the following caveats:
255
+
256
+ 1. You need to use an **asynchronous** function. Even if your function is synchronous, it will be called as an asynchronous one—and as you know, there is a performance degradation when using asynchronous operations
257
+ 2. **Stay away** from using `runHook` inside a hook! Even though it's possible, your code will become unreadable and messy pretty soon.
258
+ 3. It's hard to trace errors in a hook. Because of its sequential nature, if a handler that's called earlier than yours throws an error, your hook won't be called at all.
259
+ 4. If you use so many plugins that use the hook system so extensively with so many files, your app's boot time can take much longer than it's supposed to.
260
+
261
+ Our advice is to **use it wisely**. Don't use hooks unless necessary; this will make your app or plugin clean and easy to understand.
262
+