waibu 2.16.1 → 2.17.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.
@@ -8,7 +8,7 @@
8
8
  "bootApp%s": "Boot app: %s",
9
9
  "cantParseXmlBodyWithout%s": "Can't parse XML body unless package '%s' is loaded first",
10
10
  "routesL": "routes",
11
- "routeDisabled%s%s": "Route %s (%s) is disabled",
11
+ "routeDisabled%s": "Route '%s' is disabled",
12
12
  "rerouted%s%s": "Rerouted %s -> %s",
13
13
  "bootSubApp%s": "Boot sub app: %s",
14
14
  "routeNotFound%s%s": "Route '%s (%s)' not found",
@@ -8,7 +8,7 @@
8
8
  "bootApp%s": "Jalankan aplikasi: %s",
9
9
  "cantParseXmlBodyWithout%s": "Tidak bisa memparse bodi XML sebelum paket '%s' dimuat terlebih dahulu",
10
10
  "routesL": "jalur",
11
- "routeDisabled%s%s": "Jalur %s (%s) dimatikan",
11
+ "routeDisabled%s": "Jalur '%s' dimatikan",
12
12
  "rerouted%s%s": "Jalur dipindahkan %s -> %s",
13
13
  "bootSubApp%s": "Boot sub app: %s",
14
14
  "routeNotFound%s%s": "Jalur '%s (%s)' tidak ditemukan",
package/index.js CHANGED
@@ -110,7 +110,10 @@ async function factory (pkgName) {
110
110
  lang: 'lang'
111
111
  },
112
112
  paramsCharMap: {},
113
- printRoutes: true,
113
+ route: {
114
+ print: true,
115
+ disabled: []
116
+ },
114
117
  pageTitleFormat: '%s : %s',
115
118
  siteInfo: {
116
119
  title: 'My Website',
@@ -184,7 +187,7 @@ async function factory (pkgName) {
184
187
  }
185
188
  cfg.factory.genReqId = req => generateId()
186
189
  cfg.factory.disableRequestLogging = true
187
- cfg.factory.querystringParser = str => this.qs.parse(str)
190
+ cfg.factory.routerOptions.querystringParser = str => this.qs.parse(str)
188
191
 
189
192
  this.instance = fastify(cfg.factory)
190
193
  this.routes = this.routes || []
@@ -201,7 +204,7 @@ async function factory (pkgName) {
201
204
  await handleHome.call(this)
202
205
  await handleNotFound.call(this)
203
206
  await this.instance.listen(cfg.server)
204
- if (cfg.printRoutes) printRoutes.call(this)
207
+ if (cfg.route.print) printRoutes.call(this)
205
208
  }
206
209
 
207
210
  /**
@@ -477,7 +480,9 @@ async function factory (pkgName) {
477
480
  const plugin = getPlugin(ns)
478
481
  const cfg = plugin.config ?? {}
479
482
  let info = {}
483
+ const neg = name[0] === '!'
480
484
  if (name.startsWith('mailto:') || name.startsWith('tel:')) return name
485
+ if (neg) name = name.slice(1)
481
486
  if (name.slice(0, 2) === ':/') name = ns + name
482
487
  if (['%', '.', '/', '?', '#'].includes(name[0]) || name.slice(1, 2) === ':') info.path = name
483
488
  else if (['~'].includes(name[0])) info.path = name.slice(1)
@@ -485,8 +490,8 @@ async function factory (pkgName) {
485
490
  info = breakNsPath(name)
486
491
  }
487
492
  if (info.path.slice(0, 2) === './') info.path = info.path.slice(2)
488
- if (this.routePathHandlers[info.subNs]) return this.routePathHandlers[info.subNs].handler(name, options)
489
- if (info.path.includes('//')) return info.path
493
+ if (this.routePathHandlers[info.subNs]) return (neg ? '!' : '') + this.routePathHandlers[info.subNs].handler(name, options)
494
+ if (info.path.includes('//')) return (neg ? '!' : '') + info.path
490
495
 
491
496
  info.path = info.path.split('/').map(p => {
492
497
  if (!(p[0] === ':' || (p[0] === '{' && p[p.length - 1] === '}'))) return p
@@ -503,7 +508,7 @@ async function factory (pkgName) {
503
508
  info.qs = defaultsDeep({}, query, info.qs)
504
509
  if (!isEmpty(info.qs)) url += '?' + this.qs.stringify(info.qs)
505
510
  if (!url.startsWith('http') && guessHost) url = `http://${this.config.server.host}:${this.config.server.port}/${trimStart(url, '/')}`
506
- return url
511
+ return (neg ? '!' : '') + url
507
512
  }
508
513
 
509
514
  /**
@@ -605,6 +610,16 @@ async function factory (pkgName) {
605
610
  if (isPlainObject(cfgValue) || isArray(cfgValue)) return defaultsDeep({}, reqValue, cfgValue, defValue)
606
611
  return reqValue ?? cfgValue ?? defValue
607
612
  }
613
+
614
+ isRouteDisabled = (path, warning = true) => {
615
+ const { outmatch } = this.app.lib
616
+ path = this.routePath(path)
617
+ const result = this.config.route.disabled.find(item => {
618
+ return outmatch(this.routePath(item))(path)
619
+ })
620
+ if (result && warning) this.log.warn('routeDisabled%s', path)
621
+ return result
622
+ }
608
623
  }
609
624
 
610
625
  return Waibu
@@ -54,15 +54,16 @@ const omitted = ['createdAt', 'updatedAt', '_immutable']
54
54
 
55
55
  async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
56
56
  const { runHook } = this.app.bajo
57
- const { set, merge, pick, get, isEmpty, find, cloneDeep, omit } = this.app.lib._
57
+ const { set, merge, pick, get, isEmpty, find, cloneDeep, omit, isFunction } = this.app.lib._
58
58
  const { req, reply } = opts
59
59
 
60
60
  const _meta = { template: tpl }
61
61
  params.page = merge(params.page ?? {}, { ns: req.ns, features: [] })
62
62
  params.sidebar = params.sidebar ?? []
63
63
  if (params.error) {
64
- if (params.error.statusCode) _meta.statusCode = params.error.statusCode
65
- _meta.errorMessage = params.error.message
64
+ for (const ns of this.app.getAllNs()) {
65
+ if (isFunction(this.app[ns].sanitizeError)) this.app[ns].sanitizeError(params.error)
66
+ }
66
67
  if (params.error.ns) params.page.ns = params.error.ns
67
68
  this.log.error('error%s', params.error.message)
68
69
  if (this.app.bajo.config.env === 'dev') console.log(params.error)
@@ -13,10 +13,12 @@ export function writeHtml (req, reply, tpl, payload) {
13
13
  export async function interceptor (name, err, req, reply) {
14
14
  const { get, trim } = this.app.lib._
15
15
  let webApp = get(req, 'routeOptions.config.webApp')
16
+ const all = this.webApps.map(item => item.ns)
16
17
  if (!webApp) {
17
18
  const url = req.url ?? req.raw.url
18
19
  const [prefix] = trim(url, '/').split('/')
19
- webApp = this.getPluginByPrefix(prefix, true)
20
+ const ns = this.getPluginByPrefix(prefix, true)
21
+ if (all.includes(ns)) webApp = ns
20
22
  }
21
23
  if (!webApp) {
22
24
  const wa = this.webApps.find(item => item.prefix === '')
@@ -30,27 +32,42 @@ export async function interceptor (name, err, req, reply) {
30
32
  }
31
33
 
32
34
  function redirSvc (req) {
33
- const { trim, find, get } = this.app.lib._
35
+ const { trim, get } = this.app.lib._
34
36
  const { outmatch } = this.app.lib
35
- let match = false
36
- let [prefix, ...args] = trim(req.url, '/').split('/')
37
- args = '/' + args.join('/')
38
- let plugin = find(this.app.getAllNs(), p => {
39
- return get(this, `app.${p}.config.waibu.prefix`) === prefix
40
- })
41
- if (!plugin) {
42
- plugin = 'main'
43
- args = `/${prefix}`
44
- }
45
- const items = get(this, `app.${plugin}.config.waibuMpa.redirect`, {})
46
- for (const k in items) {
47
- const isMatch = outmatch(k)
48
- if (isMatch(args)) {
49
- match = items[k]
50
- break
37
+
38
+ const matchRoute = (path, items) => {
39
+ let match = false
40
+ for (const k in items) {
41
+ const isMatch = outmatch(k)
42
+ if (isMatch(path)) {
43
+ match = items[k]
44
+ const parts = path.split('/')
45
+ const patterns = k.split('/')
46
+ for (const idx in patterns) {
47
+ if (patterns[idx] === '*') match = match.replace(`{${idx}}`, parts[idx])
48
+ }
49
+ break
50
+ }
51
51
  }
52
+ return match
53
+ }
54
+
55
+ const [prefix, subPrefix, ...args] = trim(req.url.split('?')[0].split('#')[0], '/').split('/')
56
+ let plugin = this.getPluginByPrefix(prefix)
57
+ const subPlugin = this.getPluginByPrefix(subPrefix)
58
+ if (!plugin && !subPlugin) plugin = this.app.main
59
+ let route = false
60
+ if (plugin && subPlugin) {
61
+ const items = get(this, `app.${subPlugin.ns}.config.waibuMpa.redirectSubRoute.${plugin.ns}`, {})
62
+ route = matchRoute(`/${args.join('/')}`, items)
63
+ if (route) return route
64
+ }
65
+ if (plugin) {
66
+ const items = get(this, `app.${plugin.ns}.config.waibuMpa.redirect`, {})
67
+ route = matchRoute(subPrefix ? `/${subPrefix}/${args.join('/')}` : '/', items)
68
+ if (route) return route
52
69
  }
53
- return match
70
+ return route
54
71
  }
55
72
 
56
73
  export async function notFound (err, req, reply) {
package/lib/web-app.js CHANGED
@@ -8,7 +8,7 @@ export async function collect (glob = 'boot.js', baseNs) {
8
8
  const { ns, alias, config } = this
9
9
  const mod = await importModule(file, { asHandler: true })
10
10
  mod.prefix = get(config, 'waibu.prefix', alias)
11
- if (get(config, 'intl.detectors', []).includes('path')) mod.prefix = `/:lang${mod.prefix}`
11
+ if (get(config, 'intl.detectors', []).includes('path')) mod.prefix = `:lang${mod.prefix === '' ? '' : ('/' + mod.prefix)}`
12
12
  mod.ns = ns
13
13
  mod.alias = alias
14
14
  mods.push(mod)
@@ -28,11 +28,6 @@ async function webApp () {
28
28
  await runHook(`${this.ns}:beforeAppBoot`)
29
29
  // build routes
30
30
  for (const m of this.webApps) {
31
- const disabled = this.app[m.ns].config.disabled
32
- if (Array.isArray(disabled) && disabled.length === 1 && ['*', 'all'].includes(disabled[0])) {
33
- this.log.warn('allRoutesConfigDisabled%s', m.ns)
34
- continue
35
- }
36
31
  const plugin = this.app[m.ns]
37
32
  await runHook(`${this.ns}.${m.ns}:beforeAppBoot`)
38
33
  this.log.debug('bootApp%s', m.ns)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu",
3
- "version": "2.16.1",
3
+ "version": "2.17.0",
4
4
  "description": "Web Framework for Bajo",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-06-10
4
+
5
+ - [2.17.0] Add ```config.print```
6
+ - [2.17.0] Bug fix in ```factory.routeOptions```
7
+ - [2.17.0] Add support for inversed route in ```routePath()```
8
+ - [2.17.0] Disabled routes now handled directly by ```waibu```, not its web apps
9
+
10
+ ## 2026-06-05
11
+
12
+ - [2.16.2] Bug fix in ```build-locals.js```
13
+ - [2.16.2] Bug fix in ```handle-not-found.js```
14
+
3
15
  ## 2026-06-03
4
16
 
5
17
  - [2.16.1] Bug fix in ```handle-redirect.js```
@@ -1,33 +0,0 @@
1
- function santizeMethods (methods = '*') {
2
- if (['*', 'all'].includes(methods)) methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
3
- else methods = methods.split(',').map(s => s.trim())
4
- return methods
5
- }
6
-
7
- async function isRouteDisabled (url, method, matchers = []) {
8
- const { outmatch } = this.app.lib
9
- const { isString, intersection, cloneDeep } = this.app.lib._
10
- const items = []
11
- for (let m of cloneDeep(matchers)) {
12
- if (isString(m)) m = { path: m }
13
- try {
14
- m.path = this.app.waibu.routePath(m.path)
15
- m.methods = santizeMethods(m.methods)
16
- items.push(m)
17
- } catch (err) {
18
- throw this.error(`${m.path}: ${err.message}`)
19
- }
20
- }
21
- const matcher = items.find(i => {
22
- const isMatch = outmatch(i.path)
23
- return isMatch(url)
24
- })
25
- if (!matcher) return false
26
- if (Array.isArray(method)) {
27
- const result = intersection(method, matcher.methods)
28
- return result.length > 0
29
- }
30
- return matcher.methods.includes(method)
31
- }
32
-
33
- export default isRouteDisabled