waibu 2.0.0 → 2.0.1

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 (71) 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 +566 -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/index.js +267 -51
  45. package/lib/app-hook.js +2 -2
  46. package/lib/app.js +8 -8
  47. package/lib/build-locals.js +8 -8
  48. package/lib/collect-route-path-handlers.js +2 -2
  49. package/lib/handle-download.js +2 -2
  50. package/lib/handle-forward.js +1 -1
  51. package/lib/home.js +2 -2
  52. package/lib/{log-routes.js → print-routes.js} +3 -3
  53. package/lib/webapp-scope/attach-intl.js +4 -4
  54. package/lib/webapp-scope/handle-compress.js +1 -1
  55. package/lib/webapp-scope/handle-cors.js +1 -1
  56. package/lib/webapp-scope/handle-helmet.js +1 -1
  57. package/lib/webapp-scope/handle-multipart-body.js +3 -3
  58. package/lib/webapp-scope/handle-rate-limit.js +2 -2
  59. package/lib/webapp-scope/is-route-disabled.js +2 -2
  60. package/lib/webapp-scope/merge-route-hooks.js +2 -2
  61. package/lib/webapp-scope/route-hook.js +1 -1
  62. package/package.json +7 -2
  63. package/wiki/CONFIG.md +45 -0
  64. package/wiki/CONTRIBUTING.md +5 -0
  65. package/wiki/DEV-GUIDE.md +1 -0
  66. package/wiki/ECOSYSTEM.md +19 -0
  67. package/wiki/GETTING-STARTED.md +76 -0
  68. package/wiki/USER-GUIDE.md +1 -0
  69. package/extend/bajo/hook/on-close.js +0 -5
  70. package/extend/bajo/hook/on-ready.js +0 -5
  71. /package/extend/bajo/hook/{on-route.js → waibu@on-route.js} +0 -0
@@ -0,0 +1,566 @@
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.pluginClass.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: 7771
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 { generateId, runHook } = this.app.bajo
182
+ const cfg = this.getConfig()
183
+ if (this.app.bajoLogger) {
184
+ cfg.factory.loggerInstance = this.app.bajoLogger.instance.child(
185
+ {},
186
+ { msgPrefix: '[waibu] ' }
187
+ )
188
+ }
189
+ cfg.factory.genReqId = req => generateId()
190
+ cfg.factory.disableRequestLogging = true
191
+ cfg.factory.querystringParser = str => this.qs.parse(str)
192
+
193
+ const instance = fastify(cfg.factory)
194
+ instance.decorateRequest('lang', null)
195
+ instance.decorateRequest('t', () => {})
196
+ instance.decorateRequest('format', () => {})
197
+ instance.decorateRequest('langDetector', null)
198
+ instance.decorateRequest('site', null)
199
+ instance.decorateRequest('ns', null)
200
+ this.instance = instance
201
+ this.routes = this.routes || []
202
+ await runHook('waibu:afterCreateContext', instance)
203
+ await instance.register(sensible)
204
+ if (cfg.underPressure) await instance.register(underPressure)
205
+ if (cfg.noIcon) await instance.register(noIcon)
206
+ await handleRedirect.call(this, instance)
207
+ await handleForward.call(this, instance)
208
+ await appHook.call(this)
209
+ await routeHook.call(this, this.ns)
210
+ await boot.call(this)
211
+ await instance.listen(cfg.server)
212
+ if (cfg.printRoutes) printRoutes.call(this)
213
+ }
214
+
215
+ /**
216
+ * Exit handler
217
+ *
218
+ * @method
219
+ * @async
220
+ */
221
+ exit = async () => {
222
+ this.instance.close()
223
+ }
224
+
225
+ /**
226
+ * Find route by route name
227
+ *
228
+ * @param {string} name - ns based route name
229
+ * @returns {Object} Route object
230
+ */
231
+ findRoute = (name) => {
232
+ const { outmatch } = this.app.lib
233
+ const { find } = this.app.lib._
234
+ const { breakNsPath } = this.app.bajo
235
+ let { ns, subNs = '', path } = breakNsPath(name)
236
+ const params = path.split('|')
237
+ if (params.length > 1) path = params[0]
238
+ return find(this.routes, r => {
239
+ if (r.path.startsWith('*')) return false
240
+ r.config = r.config ?? {}
241
+ const match = outmatch(r.config.pathSrc ?? r.path, { separator: false })
242
+ if (!match(path)) return false
243
+ return ns === r.config.ns &amp;&amp; r.config.subNs === subNs
244
+ })
245
+ }
246
+
247
+ get escapeChars () {
248
+ return this.constructor.escapeChars
249
+ }
250
+
251
+ /**
252
+ * Escape text
253
+ *
254
+ * @method
255
+ * @param {string} text
256
+ * @returns {string}
257
+ */
258
+ escape = (text = '') => {
259
+ if (typeof text !== 'string') return text
260
+ const { forOwn } = this.app.lib._
261
+ forOwn(this.escapeChars, (v, k) => {
262
+ text = text.replaceAll(k, v)
263
+ })
264
+ return text
265
+ }
266
+
267
+ /**
268
+ * Fetch something from url. A wrapper of bajo-extra's fetchUrl which support
269
+ * bajo's ns based url.
270
+ *
271
+ * @method
272
+ * @async
273
+ * @param {string} url - Also support ns based url
274
+ * @param {Object} [opts={}] - node's fetch options
275
+ * @param {Object} [extra={}] - See {@link https://ardhi.github.io/bajo-extra|bajo-extra}
276
+ * @returns {Object}
277
+ */
278
+ fetch = async (url, opts = {}, extra = {}) => {
279
+ const { fetch } = this.app.bajoExtra
280
+ extra.rawResponse = true
281
+
282
+ url = this.routePath(url, { guessHost: true })
283
+ const resp = await fetch(url, opts, extra)
284
+ const result = await resp.json()
285
+ if (!resp.ok) {
286
+ throw this.error(result.message, {
287
+ statusCode: resp.status,
288
+ success: false
289
+ })
290
+ }
291
+ return result
292
+ }
293
+
294
+ /**
295
+ * Get visitor IP from fastify's request object
296
+ *
297
+ * @method
298
+ * @param {Object} req - request object
299
+ * @returns {string}
300
+ */
301
+ getIp = (req) => {
302
+ const { isEmpty } = this.app.lib._
303
+ let fwd = req.headers['x-forwarded-for'] ?? ''
304
+ if (!Array.isArray(fwd)) fwd = fwd.split(',').map(ip => ip.trim())
305
+ return isEmpty(fwd[0]) ? req.ip : fwd[0]
306
+ }
307
+
308
+ /**
309
+ * Get origin of fastify's request object
310
+ *
311
+ * @method
312
+ * @param {Object} req
313
+ * @returns {string}
314
+ */
315
+ getOrigin = (req) => {
316
+ const { isEmpty } = this.app.lib._
317
+ let host = req.host
318
+ if (isEmpty(host) || host === ':authority') host = `${this.config.server.host}:${this.config.server.port}`
319
+ return `${req.protocol}://${host}`
320
+ }
321
+
322
+ /**
323
+ * Get plugin by prefix
324
+ *
325
+ * @method
326
+ * @param {string} prefix
327
+ * @returns {Object}
328
+ */
329
+ getPluginByPrefix = (prefix) => {
330
+ const { get, find } = this.app.lib._
331
+ const item = find(this.app.waibu.routes, r => {
332
+ return get(r, 'config.prefix') === prefix
333
+ })
334
+ const ns = get(item, 'config.ns')
335
+ if (ns) return this.app[ns]
336
+ }
337
+
338
+ /**
339
+ * Get plugin's prefix by name
340
+ *
341
+ * @method
342
+ * @param {string} name - Plugin's name
343
+ * @param {string} [webApp=waibuMpa] - Web app to use
344
+ * @returns {string}
345
+ */
346
+ getPluginPrefix = (name, webApp = 'waibuMpa') => {
347
+ const { get, trim } = this.app.lib._
348
+ let prefix = get(this, `app.${name}.config.${webApp}.prefix`, get(this, `app.${name}.config.waibu.prefix`, this.app[name].alias))
349
+ if (name === 'main') {
350
+ const cfg = this.app[webApp].config
351
+ if (cfg.mountMainAsRoot) prefix = ''
352
+ }
353
+ return trim(prefix, '/')
354
+ }
355
+
356
+ /**
357
+ * Get all available routes
358
+ *
359
+ * @method
360
+ * @param {boolean} [grouped=false] - Returns as groups of urls and methods
361
+ * @param {*} [lite=false] - Retuns only urls and methods
362
+ * @returns {Array}
363
+ */
364
+ getRoutes = (grouped = false, lite = false) => {
365
+ const { groupBy, orderBy, mapValues, map, pick } = this.app.lib._
366
+ const all = this.routes
367
+ let routes
368
+ if (grouped) {
369
+ const group = groupBy(orderBy(all, ['url', 'method']), 'url')
370
+ routes = lite ? mapValues(group, (v, k) => map(v, 'method')) : group
371
+ } else if (lite) routes = map(all, a => pick(a, ['url', 'method']))
372
+ else routes = all
373
+ return routes
374
+ }
375
+
376
+ /**
377
+ * Get uploaded files by request ID
378
+ *
379
+ * @method
380
+ * @param {string} reqId - Request ID
381
+ * @param {boolean} [fileUrl=false] - If ```true```, files returned as file url format (```file:///...```)
382
+ * @param {*} returnDir - If ```true```, also return its directory
383
+ * @returns {(Object|Array)} - Returns object if ```returnDir``` is ```true```, array of files otherwise
384
+ */
385
+ getUploadedFiles = async (reqId, fileUrl = false, returnDir = false) => {
386
+ const { getPluginDataDir, resolvePath } = this.app.bajo
387
+ const { fastGlob } = this.app.lib
388
+ const dir = `${getPluginDataDir(this.ns)}/upload/${reqId}`
389
+ const result = await fastGlob(`${dir}/*`)
390
+ if (!fileUrl) return returnDir ? { dir, files: result } : result
391
+ const files = result.map(f => resolvePath(f, true))
392
+ return returnDir ? { dir, files } : files
393
+ }
394
+
395
+ /**
396
+ * Is namespace's path contains language detector token?
397
+ *
398
+ * @method
399
+ * @param {string} ns - Plugin name
400
+ * @returns {boolean}
401
+ */
402
+ isIntlPath = (ns) => {
403
+ const { get } = this.app.lib._
404
+ return get(this.app[ns], 'config.intl.detectors', []).includes('path')
405
+ }
406
+
407
+ notFound = (name, options) => {
408
+ throw this.error('_notFound', { path: name })
409
+ }
410
+
411
+ /**
412
+ * Parse filter found from Fastify's request based on keys set in config object
413
+ *
414
+ * @method
415
+ * @param {Object} req - Request object
416
+ * @returns {Object}
417
+ */
418
+ parseFilter = (req) => {
419
+ const result = {}
420
+ const items = Object.keys(this.config.qsKey)
421
+ for (const item of items) {
422
+ result[item] = req.query[this.config.qsKey[item]]
423
+ }
424
+ return result
425
+ }
426
+
427
+ /**
428
+ * Get route directory by plugin's name
429
+ *
430
+ * @param {*} ns - Namespace
431
+ * @param {*} [baseNs] - Base namespace. If not provided, defaults to scope's ns
432
+ * @returns {string}
433
+ */
434
+ routeDir = (ns, baseNs) => {
435
+ const { get } = this.app.lib._
436
+ if (!baseNs) baseNs = ns
437
+ const cfg = this.app[baseNs].config
438
+ const prefix = get(cfg, 'waibu.prefix', this.app[baseNs].alias)
439
+ const dir = prefix === '' ? '' : `/${prefix}`
440
+ const cfgMpa = get(this, 'app.waibuMpa.config')
441
+ if (ns === this.app.mainNs &amp;&amp; cfgMpa.mountMainAsRoot) return ''
442
+ if (ns === baseNs) return dir
443
+ return dir + `/${get(this.app[ns].config, 'waibu.prefix', this.app[ns].alias)}`
444
+ }
445
+
446
+ /**
447
+ * Get route path by route's name:
448
+ * - If it is a ```mailto:``` or ```tel:``` url, it returns as is
449
+ * - If it is a ns based name, it will be parsed first
450
+ *
451
+ * @method
452
+ * @param {string} name
453
+ * @param {Object} [options={}] - Options object
454
+ * @param {string} [options.base=waibu] - Base namespace
455
+ * @param {boolean} [options.guessHost] - If true, guest host if host is not set
456
+ * @param {Object} [options.query={}] - Query string's object. If provided, it will be added to returned value
457
+ * @param {Object} [options.params={}] - Parameter object. If provided, it will be merged to returned value
458
+ * @returns {string}
459
+ */
460
+ routePath = (name, options = {}) => {
461
+ const { getPlugin } = this.app.bajo
462
+ const { defaultsDeep } = this.app.lib.aneka
463
+ const { isEmpty, get, trimEnd, trimStart } = this.app.lib._
464
+ const { breakNsPath } = this.app.bajo
465
+ const { query = {}, base = this.ns, params = {}, guessHost } = options
466
+
467
+ const plugin = getPlugin(base)
468
+ const cfg = plugin.config ?? {}
469
+ let info = {}
470
+ if (name.startsWith('mailto:') || name.startsWith('tel:')) return name
471
+ if (['%', '.', '/', '?', '#'].includes(name[0]) || name.slice(1, 2) === ':') info.path = name
472
+ else if (['~'].includes(name[0])) info.path = name.slice(1)
473
+ else {
474
+ info = breakNsPath(name)
475
+ }
476
+ if (info.path.slice(0, 2) === './') info.path = info.path.slice(2)
477
+ if (this.routePathHandlers[info.subNs]) return this.routePathHandlers[info.subNs].handler(name, options)
478
+ if (info.path.includes('//')) return info.path
479
+
480
+ info.path = info.path.split('/').map(p => {
481
+ return p[0] === ':' &amp;&amp; params[p.slice(1)] ? params[p.slice(1)] : p
482
+ }).join('/')
483
+ let url = info.path
484
+ const langDetector = get(cfg, 'intl.detectors', [])
485
+ if (info.ns) url = trimEnd(langDetector.includes('path') ? `/${params.lang ?? ''}${this.routeDir(info.ns)}${info.path}` : `${this.routeDir(info.ns)}${info.path}`, '/')
486
+ if (options.uriEncoded) url = url.split('/').map(u => encodeURI(u)).join('/')
487
+ info.qs = defaultsDeep({}, query, info.qs)
488
+ if (!isEmpty(info.qs)) url += '?' + this.qs.stringify(info.qs)
489
+ if (!url.startsWith('http') &amp;&amp; guessHost) url = `http://${this.config.server.host}:${this.config.server.port}/${trimStart(url, '/')}`
490
+ return url
491
+ }
492
+
493
+ /**
494
+ * Method to send mail through Masohi Messaging System. It is a thin wrapper
495
+ * for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
496
+ *
497
+ * If masohi is not loaded, nothing is delivered.
498
+ *
499
+ * @method
500
+ * @async
501
+ * @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
502
+ * @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
503
+ * @returns
504
+ */
505
+ sendMail = async (tpl, { to, cc, bcc, from, subject, data = {}, conn, source, options = {} }) => {
506
+ conn = conn ?? 'masohiMail:default'
507
+ if (!this.app.masohi || !this.app.masohiMail) return
508
+ const { get, isString } = this.app.lib._
509
+ const { generateId } = this.app.bajo
510
+ const { render } = this.app.bajoTemplate
511
+ if (isString(tpl)) tpl = [tpl]
512
+ const locals = await buildLocals.call(this, { tpl, params: data, opts: options })
513
+ const opts = {
514
+ lang: get(options, 'req.lang'),
515
+ groupId: get(options, 'req.id', generateId())
516
+ }
517
+ const message = await render(tpl[0], locals, opts)
518
+ if (tpl[1]) opts.messageText = await render(tpl[1], locals, opts)
519
+ const payload = { type: 'object', data: { to, cc, bcc, from, subject, message, options: opts } }
520
+ await this.app.masohi.send({ payload, source: source ?? this.ns, conn }) // mail sent through worker
521
+ }
522
+
523
+ /**
524
+ * Recursively unescape block of texts
525
+ *
526
+ * @method
527
+ * @param {string} content - Source content
528
+ * @param {string} start - Block's start
529
+ * @param {string} end - Block's end
530
+ * @param {string} startReplacer - Token to use as block's start replacer
531
+ * @param {string} endReplacer - Token to use as block's end replacer
532
+ * @returns {string}
533
+ */
534
+ unescapeBlock = (content, start, end, startReplacer, endReplacer) => {
535
+ const { extractText } = this.app.lib.aneka
536
+ const { result } = extractText(content, start, end)
537
+ if (result.length === 0) return content
538
+ const unescaped = this.unescape(result)
539
+ const token = `${start}${result}${end}`
540
+ const replacer = `${startReplacer}${unescaped}${endReplacer}`
541
+ const block = content.replaceAll(token, replacer)
542
+ return this.unescapeBlock(block, start, end, startReplacer, endReplacer)
543
+ }
544
+
545
+ /**
546
+ * Unescape text using {@link TEscapeChars} rules
547
+ *
548
+ * @method
549
+ * @param {string} text - Text to unescape
550
+ * @returns {string}
551
+ */
552
+ unescape = (text) => {
553
+ const { forOwn, invert } = this.app.lib._
554
+ const mapping = invert(this.escapeChars)
555
+ forOwn(mapping, (v, k) => {
556
+ text = text.replaceAll(k, v)
557
+ })
558
+ return text
559
+ }
560
+ }
561
+
562
+ return Waibu
563
+ }
564
+
565
+ export default factory
566
+ </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>