waibu 2.0.0 → 2.1.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 (81) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +45 -0
  4. package/LICENSE +1 -1
  5. package/README.md +40 -7
  6. package/config-prod.json +1 -1
  7. package/docs/Waibu.html +3 -0
  8. package/docs/data/search.json +1 -0
  9. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  10. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  11. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  12. package/docs/global.html +3 -0
  13. package/docs/index.html +3 -0
  14. package/docs/index.js.html +568 -0
  15. package/docs/scripts/core.js +726 -0
  16. package/docs/scripts/core.min.js +23 -0
  17. package/docs/scripts/resize.js +90 -0
  18. package/docs/scripts/search.js +265 -0
  19. package/docs/scripts/search.min.js +6 -0
  20. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  21. package/docs/scripts/third-party/fuse.js +9 -0
  22. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  23. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  24. package/docs/scripts/third-party/hljs-original.js +5171 -0
  25. package/docs/scripts/third-party/hljs.js +1 -0
  26. package/docs/scripts/third-party/popper.js +5 -0
  27. package/docs/scripts/third-party/tippy.js +1 -0
  28. package/docs/scripts/third-party/tocbot.js +672 -0
  29. package/docs/scripts/third-party/tocbot.min.js +1 -0
  30. package/docs/static/bitcoin.jpeg +0 -0
  31. package/docs/static/home.md +31 -0
  32. package/docs/static/logo-ecosystem.png +0 -0
  33. package/docs/static/logo.png +0 -0
  34. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  35. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  36. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  37. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  38. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  39. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  40. package/extend/bajo/hook/waibu@on-close.js +5 -0
  41. package/extend/bajo/hook/waibu@on-ready.js +5 -0
  42. package/extend/bajo/hook/{on-request.js → waibu@on-request.js} +6 -6
  43. package/extend/bajo/hook/{on-response.js → waibu@on-response.js} +2 -2
  44. package/extend/bajo/hook/{on-route.js → waibu@on-route.js} +0 -0
  45. package/extend/bajo/intl/en-US.json +0 -0
  46. package/extend/bajo/intl/id.json +0 -0
  47. package/extend/bajoTemplate/layout/email.html +0 -0
  48. package/extend/bajoTemplate/layout/email.id.html +0 -0
  49. package/index.js +259 -55
  50. package/lib/app-hook.js +2 -2
  51. package/lib/app.js +8 -8
  52. package/lib/build-locals.js +22 -10
  53. package/lib/collect-route-path-handlers.js +2 -2
  54. package/lib/handle-download.js +2 -2
  55. package/lib/handle-forward.js +1 -1
  56. package/lib/handle-redirect.js +0 -0
  57. package/lib/handle-xml-body.js +0 -0
  58. package/lib/home.js +2 -2
  59. package/lib/{log-routes.js → print-routes.js} +3 -3
  60. package/lib/webapp-scope/attach-intl.js +4 -4
  61. package/lib/webapp-scope/error-handler.js +0 -0
  62. package/lib/webapp-scope/handle-compress.js +1 -1
  63. package/lib/webapp-scope/handle-cors.js +1 -1
  64. package/lib/webapp-scope/handle-helmet.js +1 -1
  65. package/lib/webapp-scope/handle-multipart-body.js +3 -3
  66. package/lib/webapp-scope/handle-rate-limit.js +2 -2
  67. package/lib/webapp-scope/is-route-disabled.js +2 -2
  68. package/lib/webapp-scope/merge-route-hooks.js +2 -2
  69. package/lib/webapp-scope/rerouted-path.js +0 -0
  70. package/lib/webapp-scope/route-hook.js +1 -1
  71. package/logo.png +0 -0
  72. package/package.json +24 -15
  73. package/wiki/CHANGES.md +16 -0
  74. package/wiki/CONFIG.md +45 -0
  75. package/wiki/CONTRIBUTING.md +5 -0
  76. package/wiki/DEV-GUIDE.md +1 -0
  77. package/wiki/ECOSYSTEM.md +19 -0
  78. package/wiki/GETTING-STARTED.md +76 -0
  79. package/wiki/USER-GUIDE.md +1 -0
  80. package/extend/bajo/hook/on-close.js +0 -5
  81. package/extend/bajo/hook/on-ready.js +0 -5
@@ -0,0 +1,568 @@
1
+ <!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: index.js</title><!--[if lt IE 9]>
2
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
3
+ <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Waibu API</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Waibu.html">Waibu</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/waibu" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/waibu" target="">Github</a></div><div class="navbar-item"><a id="" href="https://waibu.bajo.app/" target="">Waibu</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">index.js</h1></header><article><pre class="prettyprint source lang-js"><code>import collectRoutePathHandlers from './lib/collect-route-path-handlers.js'
4
+ import fastify from 'fastify'
5
+ import appHook from './lib/app-hook.js'
6
+ import routeHook from './lib/webapp-scope/route-hook.js'
7
+ import printRoutes from './lib/print-routes.js'
8
+ import { boot } from './lib/app.js'
9
+ import sensible from '@fastify/sensible'
10
+ import noIcon from 'fastify-no-icon'
11
+ import underPressure from '@fastify/under-pressure'
12
+ import handleForward from './lib/handle-forward.js'
13
+ import handleRedirect from './lib/handle-redirect.js'
14
+ import buildLocals from './lib/build-locals.js'
15
+ import queryString from 'query-string'
16
+
17
+ /**
18
+ * @typedef TEscapeChars
19
+ * @type {Object}
20
+ * @memberof Waibu
21
+ * @property {string} &amp;lt;=&amp;lt;
22
+ * @property {string} &amp;gt;=&amp;gt;
23
+ * @property {string} &amp;quot;=&amp;quot;
24
+ * @property {string} &amp;apos;=&amp;apos;
25
+ */
26
+
27
+ /**
28
+ * Plugin factory
29
+ *
30
+ * @param {string} pkgName - NPM package name
31
+ * @returns {class}
32
+ */
33
+ async function factory (pkgName) {
34
+ const me = this
35
+
36
+ /**
37
+ * Waibu Web Framework plugin for Bajo. This is the main foundation of all web apps attached to
38
+ * the system through a route prefix. Those web apps are then build as childrens with
39
+ * its own fastify's context.
40
+ *
41
+ * There are currently 3 web apps available:
42
+ * - {@link https://github.com/ardhi/waibu-static|waibu-static} for static content delivery
43
+ * - {@link https://github.com/ardhi/waibu-rest-api|waibu-rest-api} for rest api setup
44
+ * - and {@link https://github.com/ardhi/waibu-mpa|waibu-mpa} for normal multi-page application
45
+ *
46
+ * You should write your code as the extension of above web apps. Not to this main app.
47
+ * Unless, of course, if you want to write custom web apps with its own context.
48
+ *
49
+ * @class
50
+ */
51
+ class Waibu extends this.app.baseClass.Base {
52
+ /**
53
+ * @constant {string[]}
54
+ * @default ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler', 'preSerialization', 'onSend', 'onTimeout', 'onError']
55
+ * @memberof Waibu
56
+ */
57
+ static hookTypes = ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler',
58
+ 'preSerialization', 'onSend', 'onTimeout', 'onError']
59
+
60
+ /**
61
+ * @constant {string}
62
+ * @memberof Waibu
63
+ * @default 'w'
64
+ */
65
+ static alias = 'w'
66
+
67
+ /**
68
+ * @constant {string[]}
69
+ * @default ['bajo-extra']
70
+ * @memberof Waibu
71
+ */
72
+ static dependencies = ['bajo-extra']
73
+
74
+ /**
75
+ * @constant {TEscapeChars}
76
+ * @memberof Waibu
77
+ */
78
+ static escapeChars = {
79
+ '&lt;': '&amp;lt;',
80
+ '>': '&amp;gt;',
81
+ '"': '&amp;quot;',
82
+ "'": '&amp;apos;'
83
+ }
84
+
85
+ constructor () {
86
+ super(pkgName, me.app)
87
+
88
+ /**
89
+ * @see {@tutorial config}
90
+ * @type {Object}
91
+ */
92
+ this.config = {
93
+ home: undefined,
94
+ server: {
95
+ host: '127.0.0.1',
96
+ port: 17845
97
+ },
98
+ factory: {
99
+ trustProxy: true,
100
+ bodyLimit: 10485760,
101
+ pluginTimeout: 30000
102
+ },
103
+ deferLog: false,
104
+ prefixVirtual: '~',
105
+ qsKey: {
106
+ bbox: 'bbox',
107
+ bboxLatField: 'bboxLatField',
108
+ bboxLngField: 'bboxLngField',
109
+ query: 'query',
110
+ match: 'match',
111
+ skip: 'skip',
112
+ page: 'page',
113
+ limit: 'limit',
114
+ sort: 'sort',
115
+ fields: 'fields',
116
+ lang: 'lang'
117
+ },
118
+ paramsCharMap: {},
119
+ printRoutes: true,
120
+ pageTitleFormat: '%s : %s',
121
+ siteInfo: {
122
+ title: 'My Website',
123
+ orgName: 'My Organization'
124
+ },
125
+ cors: {},
126
+ compress: {},
127
+ helmet: {},
128
+ rateLimit: {},
129
+ multipart: {
130
+ attachFieldsToBody: true,
131
+ limits: {
132
+ parts: 100,
133
+ fileSize: 10485760
134
+ }
135
+ },
136
+ noIcon: true,
137
+ underPressure: false,
138
+ forwardOpts: {
139
+ disableRequestLogging: true,
140
+ undici: {
141
+ connections: 128,
142
+ pipelining: 1,
143
+ keepAliveTimeout: 60 * 1000,
144
+ tls: {
145
+ rejectUnauthorized: false
146
+ }
147
+ }
148
+ }
149
+ }
150
+ this.qs = {
151
+ parse: (item) => {
152
+ return queryString.parse(item, {
153
+ parseBooleans: true,
154
+ parseNumbers: true
155
+ })
156
+ },
157
+ parseUrl: queryString.parseUrl,
158
+ stringify: queryString.stringify,
159
+ stringifyUrl: queryString.stringifyUrl
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Initialize plugin
165
+ *
166
+ * @method
167
+ * @async
168
+ */
169
+ init = async () => {
170
+ if (this.config.home === '/') this.config.home = false
171
+ await collectRoutePathHandlers.call(this)
172
+ }
173
+
174
+ /**
175
+ * Start plugin
176
+ *
177
+ * @method
178
+ * @async
179
+ */
180
+ start = async () => {
181
+ const { runHook } = this.app.bajo
182
+ const { generateId } = this.app.lib.aneka
183
+ const cfg = this.getConfig()
184
+ if (this.app.bajoLogger) {
185
+ cfg.factory.loggerInstance = this.app.bajoLogger.instance.child(
186
+ {},
187
+ { msgPrefix: '[waibu] ' }
188
+ )
189
+ }
190
+ cfg.factory.genReqId = req => generateId()
191
+ cfg.factory.disableRequestLogging = true
192
+ cfg.factory.querystringParser = str => this.qs.parse(str)
193
+
194
+ const instance = fastify(cfg.factory)
195
+ instance.decorateRequest('lang', null)
196
+ instance.decorateRequest('t', () => {})
197
+ instance.decorateRequest('format', () => {})
198
+ instance.decorateRequest('langDetector', null)
199
+ instance.decorateRequest('site', null)
200
+ instance.decorateRequest('ns', null)
201
+ this.instance = instance
202
+ this.routes = this.routes || []
203
+ await runHook('waibu:afterCreateContext', instance)
204
+ await instance.register(sensible)
205
+ if (cfg.underPressure) await instance.register(underPressure)
206
+ if (cfg.noIcon) await instance.register(noIcon)
207
+ await handleRedirect.call(this, instance)
208
+ await handleForward.call(this, instance)
209
+ await appHook.call(this)
210
+ await routeHook.call(this, this.ns)
211
+ await boot.call(this)
212
+ await instance.listen(cfg.server)
213
+ if (cfg.printRoutes) printRoutes.call(this)
214
+ }
215
+
216
+ /**
217
+ * Exit handler
218
+ *
219
+ * @method
220
+ * @async
221
+ */
222
+ exit = async () => {
223
+ this.instance.close()
224
+ }
225
+
226
+ /**
227
+ * Find route by route name
228
+ *
229
+ * @param {string} name - ns based route name
230
+ * @returns {Object} Route object
231
+ */
232
+ findRoute = (name) => {
233
+ const { outmatch } = this.app.lib
234
+ const { find } = this.app.lib._
235
+ const { breakNsPath } = this.app.bajo
236
+ let { ns, subNs = '', path } = breakNsPath(name)
237
+ const params = path.split('|')
238
+ if (params.length > 1) path = params[0]
239
+ return find(this.routes, r => {
240
+ if (r.path.startsWith('*')) return false
241
+ r.config = r.config ?? {}
242
+ const match = outmatch(r.config.pathSrc ?? r.path, { separator: false })
243
+ if (!match(path)) return false
244
+ return ns === r.config.ns &amp;&amp; r.config.subNs === subNs
245
+ })
246
+ }
247
+
248
+ get escapeChars () {
249
+ return this.constructor.escapeChars
250
+ }
251
+
252
+ /**
253
+ * Escape text
254
+ *
255
+ * @method
256
+ * @param {string} text
257
+ * @returns {string}
258
+ */
259
+ escape = (text = '') => {
260
+ if (typeof text !== 'string') return text
261
+ const { forOwn } = this.app.lib._
262
+ forOwn(this.escapeChars, (v, k) => {
263
+ text = text.replaceAll(k, v)
264
+ })
265
+ return text
266
+ }
267
+
268
+ /**
269
+ * Fetch something from url. A wrapper of bajo-extra's fetchUrl which support
270
+ * bajo's ns based url.
271
+ *
272
+ * @method
273
+ * @async
274
+ * @param {string} url - Also support ns based url
275
+ * @param {Object} [opts={}] - node's fetch options
276
+ * @param {Object} [extra={}] - See {@link https://ardhi.github.io/bajo-extra|bajo-extra}
277
+ * @returns {Object}
278
+ */
279
+ fetch = async (url, opts = {}, extra = {}) => {
280
+ const { fetch } = this.app.bajoExtra
281
+ extra.rawResponse = true
282
+
283
+ url = this.routePath(url, { guessHost: true })
284
+ const resp = await fetch(url, opts, extra)
285
+ const result = await resp.json()
286
+ if (!resp.ok) {
287
+ throw this.error(result.message, {
288
+ statusCode: resp.status,
289
+ success: false
290
+ })
291
+ }
292
+ return result
293
+ }
294
+
295
+ /**
296
+ * Get visitor IP from fastify's request object
297
+ *
298
+ * @method
299
+ * @param {Object} req - request object
300
+ * @returns {string}
301
+ */
302
+ getIp = (req) => {
303
+ const { isEmpty } = this.app.lib._
304
+ let fwd = req.headers['x-forwarded-for'] ?? ''
305
+ if (!Array.isArray(fwd)) fwd = fwd.split(',').map(ip => ip.trim())
306
+ return isEmpty(fwd[0]) ? req.ip : fwd[0]
307
+ }
308
+
309
+ /**
310
+ * Get origin of fastify's request object
311
+ *
312
+ * @method
313
+ * @param {Object} req
314
+ * @returns {string}
315
+ */
316
+ getOrigin = (req) => {
317
+ const { isEmpty } = this.app.lib._
318
+ let host = req.host
319
+ if (isEmpty(host) || host === ':authority') host = `${this.config.server.host}:${this.config.server.port}`
320
+ return `${req.protocol}://${host}`
321
+ }
322
+
323
+ /**
324
+ * Get plugin by prefix
325
+ *
326
+ * @method
327
+ * @param {string} prefix
328
+ * @returns {Object}
329
+ */
330
+ getPluginByPrefix = (prefix) => {
331
+ const { get, find } = this.app.lib._
332
+ const item = find(this.app.waibu.routes, r => {
333
+ return get(r, 'config.prefix') === prefix
334
+ })
335
+ const ns = get(item, 'config.ns')
336
+ if (ns) return this.app[ns]
337
+ }
338
+
339
+ /**
340
+ * Get plugin's prefix by name
341
+ *
342
+ * @method
343
+ * @param {string} name - Plugin's name
344
+ * @param {string} [webApp=waibuMpa] - Web app to use
345
+ * @returns {string}
346
+ */
347
+ getPluginPrefix = (name, webApp = 'waibuMpa') => {
348
+ const { get, trim } = this.app.lib._
349
+ let prefix = get(this, `app.${name}.config.${webApp}.prefix`, get(this, `app.${name}.config.waibu.prefix`, this.app[name].alias))
350
+ if (name === 'main') {
351
+ const cfg = this.app[webApp].config
352
+ if (cfg.mountMainAsRoot) prefix = ''
353
+ }
354
+ return trim(prefix, '/')
355
+ }
356
+
357
+ /**
358
+ * Get all available routes
359
+ *
360
+ * @method
361
+ * @param {boolean} [grouped=false] - Returns as groups of urls and methods
362
+ * @param {*} [lite=false] - Retuns only urls and methods
363
+ * @returns {Array}
364
+ */
365
+ getRoutes = (grouped = false, lite = false) => {
366
+ const { groupBy, orderBy, mapValues, map, pick } = this.app.lib._
367
+ const all = this.routes
368
+ let routes
369
+ if (grouped) {
370
+ const group = groupBy(orderBy(all, ['url', 'method']), 'url')
371
+ routes = lite ? mapValues(group, (v, k) => map(v, 'method')) : group
372
+ } else if (lite) routes = map(all, a => pick(a, ['url', 'method']))
373
+ else routes = all
374
+ return routes
375
+ }
376
+
377
+ /**
378
+ * Get uploaded files by request ID
379
+ *
380
+ * @method
381
+ * @param {string} reqId - Request ID
382
+ * @param {boolean} [fileUrl=false] - If ```true```, files returned as file url format (```file:///...```)
383
+ * @param {*} returnDir - If ```true```, also return its directory
384
+ * @returns {(Object|Array)} - Returns object if ```returnDir``` is ```true```, array of files otherwise
385
+ */
386
+ getUploadedFiles = async (reqId, fileUrl = false, returnDir = false) => {
387
+ const { getPluginDataDir } = this.app.bajo
388
+ const { resolvePath } = this.app.lib.aneka
389
+ const { fastGlob } = this.app.lib
390
+ const dir = `${getPluginDataDir(this.ns)}/upload/${reqId}`
391
+ const result = await fastGlob(`${dir}/*`)
392
+ if (!fileUrl) return returnDir ? { dir, files: result } : result
393
+ const files = result.map(f => resolvePath(f, true))
394
+ return returnDir ? { dir, files } : files
395
+ }
396
+
397
+ /**
398
+ * Is namespace's path contains language detector token?
399
+ *
400
+ * @method
401
+ * @param {string} ns - Plugin name
402
+ * @returns {boolean}
403
+ */
404
+ isIntlPath = (ns) => {
405
+ const { get } = this.app.lib._
406
+ return get(this.app[ns], 'config.intl.detectors', []).includes('path')
407
+ }
408
+
409
+ notFound = (name, options) => {
410
+ throw this.error('_notFound', { path: name })
411
+ }
412
+
413
+ /**
414
+ * Parse filter found from Fastify's request based on keys set in config object
415
+ *
416
+ * @method
417
+ * @param {Object} req - Request object
418
+ * @returns {Object}
419
+ */
420
+ parseFilter = (req) => {
421
+ const result = {}
422
+ const items = Object.keys(this.config.qsKey)
423
+ for (const item of items) {
424
+ result[item] = req.query[this.config.qsKey[item]]
425
+ }
426
+ return result
427
+ }
428
+
429
+ /**
430
+ * Get route directory by plugin's name
431
+ *
432
+ * @param {*} ns - Namespace
433
+ * @param {*} [baseNs] - Base namespace. If not provided, defaults to scope's ns
434
+ * @returns {string}
435
+ */
436
+ routeDir = (ns, baseNs) => {
437
+ const { get } = this.app.lib._
438
+ if (!baseNs) baseNs = ns
439
+ const cfg = this.app[baseNs].config
440
+ const prefix = get(cfg, 'waibu.prefix', this.app[baseNs].alias)
441
+ const dir = prefix === '' ? '' : `/${prefix}`
442
+ const cfgMpa = get(this, 'app.waibuMpa.config')
443
+ if (ns === this.app.mainNs &amp;&amp; cfgMpa.mountMainAsRoot) return ''
444
+ if (ns === baseNs) return dir
445
+ return dir + `/${get(this.app[ns].config, 'waibu.prefix', this.app[ns].alias)}`
446
+ }
447
+
448
+ /**
449
+ * Get route path by route's name:
450
+ * - If it is a ```mailto:``` or ```tel:``` url, it returns as is
451
+ * - If it is a ns based name, it will be parsed first
452
+ *
453
+ * @method
454
+ * @param {string} name
455
+ * @param {Object} [options={}] - Options object
456
+ * @param {string} [options.base=waibu] - Base namespace
457
+ * @param {boolean} [options.guessHost] - If true, guest host if host is not set
458
+ * @param {Object} [options.query={}] - Query string's object. If provided, it will be added to returned value
459
+ * @param {Object} [options.params={}] - Parameter object. If provided, it will be merged to returned value
460
+ * @returns {string}
461
+ */
462
+ routePath = (name, options = {}) => {
463
+ const { getPlugin } = this.app.bajo
464
+ const { defaultsDeep } = this.app.lib.aneka
465
+ const { isEmpty, get, trimEnd, trimStart } = this.app.lib._
466
+ const { breakNsPath } = this.app.bajo
467
+ const { query = {}, base = this.ns, params = {}, guessHost } = options
468
+
469
+ const plugin = getPlugin(base)
470
+ const cfg = plugin.config ?? {}
471
+ let info = {}
472
+ if (name.startsWith('mailto:') || name.startsWith('tel:')) return name
473
+ if (['%', '.', '/', '?', '#'].includes(name[0]) || name.slice(1, 2) === ':') info.path = name
474
+ else if (['~'].includes(name[0])) info.path = name.slice(1)
475
+ else {
476
+ info = breakNsPath(name)
477
+ }
478
+ if (info.path.slice(0, 2) === './') info.path = info.path.slice(2)
479
+ if (this.routePathHandlers[info.subNs]) return this.routePathHandlers[info.subNs].handler(name, options)
480
+ if (info.path.includes('//')) return info.path
481
+
482
+ info.path = info.path.split('/').map(p => {
483
+ return p[0] === ':' &amp;&amp; params[p.slice(1)] ? params[p.slice(1)] : p
484
+ }).join('/')
485
+ let url = info.path
486
+ const langDetector = get(cfg, 'intl.detectors', [])
487
+ if (info.ns) url = trimEnd(langDetector.includes('path') ? `/${params.lang ?? ''}${this.routeDir(info.ns)}${info.path}` : `${this.routeDir(info.ns)}${info.path}`, '/')
488
+ if (options.uriEncoded) url = url.split('/').map(u => encodeURI(u)).join('/')
489
+ info.qs = defaultsDeep({}, query, info.qs)
490
+ if (!isEmpty(info.qs)) url += '?' + this.qs.stringify(info.qs)
491
+ if (!url.startsWith('http') &amp;&amp; guessHost) url = `http://${this.config.server.host}:${this.config.server.port}/${trimStart(url, '/')}`
492
+ return url
493
+ }
494
+
495
+ /**
496
+ * Method to send mail through Masohi Messaging System. It is a thin wrapper
497
+ * for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
498
+ *
499
+ * If masohi is not loaded, nothing is delivered.
500
+ *
501
+ * @method
502
+ * @async
503
+ * @param {(string|Array)} tpl - Mail's template to use. If a string is given, the same template will be used for html &amp; plaintext versions. Otherwise, the first template will be used for html mail, and the second one is for it's plaintext version
504
+ * @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
505
+ * @returns
506
+ */
507
+ sendMail = async (tpl, { to, cc, bcc, from, subject, data = {}, conn, source, options = {} }) => {
508
+ conn = conn ?? 'masohiMail:default'
509
+ if (!this.app.masohi || !this.app.masohiMail) return
510
+ const { get, isString } = this.app.lib._
511
+ const { generateId } = this.app.lib.aneka
512
+ const { render } = this.app.bajoTemplate
513
+ if (isString(tpl)) tpl = [tpl]
514
+ const locals = await buildLocals.call(this, { tpl, params: data, opts: options })
515
+ const opts = {
516
+ lang: get(options, 'req.lang'),
517
+ groupId: get(options, 'req.id', generateId())
518
+ }
519
+ const message = await render(tpl[0], locals, opts)
520
+ if (tpl[1]) opts.messageText = await render(tpl[1], locals, opts)
521
+ const payload = { type: 'object', data: { to, cc, bcc, from, subject, message, options: opts } }
522
+ await this.app.masohi.send({ payload, source: source ?? this.ns, conn }) // mail sent through worker
523
+ }
524
+
525
+ /**
526
+ * Recursively unescape block of texts
527
+ *
528
+ * @method
529
+ * @param {string} content - Source content
530
+ * @param {string} start - Block's start
531
+ * @param {string} end - Block's end
532
+ * @param {string} startReplacer - Token to use as block's start replacer
533
+ * @param {string} endReplacer - Token to use as block's end replacer
534
+ * @returns {string}
535
+ */
536
+ unescapeBlock = (content, start, end, startReplacer, endReplacer) => {
537
+ const { extractText } = this.app.lib.aneka
538
+ const { result } = extractText(content, start, end)
539
+ if (result.length === 0) return content
540
+ const unescaped = this.unescape(result)
541
+ const token = `${start}${result}${end}`
542
+ const replacer = `${startReplacer}${unescaped}${endReplacer}`
543
+ const block = content.replaceAll(token, replacer)
544
+ return this.unescapeBlock(block, start, end, startReplacer, endReplacer)
545
+ }
546
+
547
+ /**
548
+ * Unescape text using {@link TEscapeChars} rules
549
+ *
550
+ * @method
551
+ * @param {string} text - Text to unescape
552
+ * @returns {string}
553
+ */
554
+ unescape = (text) => {
555
+ const { forOwn, invert } = this.app.lib._
556
+ const mapping = invert(this.escapeChars)
557
+ forOwn(mapping, (v, k) => {
558
+ text = text.replaceAll(k, v)
559
+ })
560
+ return text
561
+ }
562
+ }
563
+
564
+ return Waibu
565
+ }
566
+
567
+ export default factory
568
+ </code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Waibu API</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/waibu" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/waibu" target="">Github</a></div><div class="navbar-item"><a id="" href="https://waibu.bajo.app/" target="">Waibu</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Waibu.html">Waibu</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>