waibu 2.2.0 → 2.3.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.
@@ -0,0 +1,10 @@
1
+ const waibuPreParsing = {
2
+ level: 9,
3
+ handler: async function (req, reply) {
4
+ const { importModule } = this.app.bajo
5
+ const attachIntl = await importModule('waibu:/lib/webapp-scope/attach-intl.js')
6
+ await attachIntl.call(this, this.config.intl.detectors, req, reply)
7
+ }
8
+ }
9
+
10
+ export default waibuPreParsing
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+
4
+ </head>
5
+ <body>
6
+ <%= text %>
7
+ </body>
8
+ </html>
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+
4
+ </head>
5
+ <body>
6
+ <%= text %>
7
+ </body>
8
+ </html>
package/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import collectRoutePathHandlers from './lib/collect-route-path-handlers.js'
2
2
  import fastify from 'fastify'
3
- import appHook from './lib/app-hook.js'
3
+ import handleAppHook from './lib/handle-app-hook.js'
4
4
  import routeHook from './lib/webapp-scope/route-hook.js'
5
5
  import printRoutes from './lib/print-routes.js'
6
- import { boot } from './lib/app.js'
6
+ import webApp from './lib/web-app.js'
7
7
  import sensible from '@fastify/sensible'
8
8
  import underPressure from '@fastify/under-pressure'
9
9
  import handleForward from './lib/handle-forward.js'
10
10
  import handleRedirect from './lib/handle-redirect.js'
11
11
  import handleFavicon from './lib/handle-favicon.js'
12
+ import handleError from './lib/handle-error.js'
13
+ import handleNotFound from './lib/handle-not-found.js'
14
+ import handleHome from './lib/handle-home.js'
12
15
  import buildLocals from './lib/build-locals.js'
13
16
  import queryString from 'query-string'
14
17
 
@@ -82,7 +85,12 @@ async function factory (pkgName) {
82
85
  factory: {
83
86
  trustProxy: true,
84
87
  bodyLimit: 10485760,
85
- pluginTimeout: 30000
88
+ pluginTimeout: 30000,
89
+ routerOptions: {
90
+ }
91
+ },
92
+ intl: {
93
+ detectors: ['qs']
86
94
  },
87
95
  deferLog: false,
88
96
  prefixVirtual: '~',
@@ -175,25 +183,27 @@ async function factory (pkgName) {
175
183
  cfg.factory.disableRequestLogging = true
176
184
  cfg.factory.querystringParser = str => this.qs.parse(str)
177
185
 
178
- const instance = fastify(cfg.factory)
179
- instance.decorateRequest('lang', null)
180
- instance.decorateRequest('t', () => {})
181
- instance.decorateRequest('format', () => {})
182
- instance.decorateRequest('langDetector', null)
183
- instance.decorateRequest('site', null)
184
- instance.decorateRequest('ns', null)
185
- this.instance = instance
186
+ this.instance = fastify(cfg.factory)
187
+ this.instance.decorateRequest('lang', null)
188
+ this.instance.decorateRequest('t', () => {})
189
+ this.instance.decorateRequest('format', () => {})
190
+ this.instance.decorateRequest('langDetector', null)
191
+ this.instance.decorateRequest('site', null)
192
+ this.instance.decorateRequest('ns', null)
186
193
  this.routes = this.routes || []
187
- await runHook('waibu:afterCreateContext', instance)
188
- await instance.register(sensible)
189
- if (cfg.underPressure) await instance.register(underPressure)
190
- await handleFavicon.call(this, instance)
191
- await handleRedirect.call(this, instance)
192
- await handleForward.call(this, instance)
193
- await appHook.call(this)
194
+ await runHook('waibu:afterCreateContext', this.instance)
195
+ await this.instance.register(sensible)
196
+ if (cfg.underPressure) await this.instance.register(underPressure)
197
+ await handleFavicon.call(this)
198
+ await handleRedirect.call(this)
199
+ await handleForward.call(this)
200
+ await handleAppHook.call(this)
201
+ await handleError.call(this)
194
202
  await routeHook.call(this, this.ns)
195
- await boot.call(this)
196
- await instance.listen(cfg.server)
203
+ await webApp.call(this)
204
+ await handleHome.call(this)
205
+ await handleNotFound.call(this)
206
+ await this.instance.listen(cfg.server)
197
207
  if (cfg.printRoutes) printRoutes.call(this)
198
208
  }
199
209
 
@@ -1,4 +1,4 @@
1
- async function appHook () {
1
+ async function handleAppHook () {
2
2
  const { runHook } = this.app.bajo
3
3
  const hooks = ['onReady', 'onClose', 'preClose', 'onRoute', 'onRegister']
4
4
  const me = this
@@ -11,4 +11,4 @@ async function appHook () {
11
11
  }
12
12
  }
13
13
 
14
- export default appHook
14
+ export default handleAppHook
@@ -0,0 +1,25 @@
1
+ import { redirect } from './handle-redirect.js'
2
+ import { notFound, writeHtml } from './handle-not-found.js'
3
+
4
+ async function error (req, reply, err = {}) {
5
+ const { get } = this.app.lib._
6
+ const webApp = get(req, 'routeOptions.config.webApp')
7
+ this.log.error(err)
8
+ if (webApp) {
9
+ const plugin = this.app[webApp]
10
+ const errorHandler = get(plugin, 'waibuFactory.errorHandler')
11
+ if (errorHandler) return await errorHandler.call(plugin, err, req, reply)
12
+ }
13
+ return writeHtml.call(this, req, reply, `${this.ns}:/extend/bajoTemplate/template/500.html`, { text: this.app.log.getErrorMessage(err) })
14
+ }
15
+
16
+ async function handleError () {
17
+ const me = this
18
+ this.instance.setErrorHandler(async function (err, req, reply) {
19
+ if (err.message === '_notFound' || err.statusCode === 404) return await notFound.call(me, req, reply, err)
20
+ if (err.message === '_redirect' && err.redirect) return redirect.call(me, reply, err.redirect, err.options)
21
+ return await error.call(me, req, reply, err)
22
+ })
23
+ }
24
+
25
+ export default handleError
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
  import handleDownload from './handle-download.js'
3
3
 
4
- async function handleFavicon (ctx) {
4
+ async function handleFavicon () {
5
5
  const { getPluginFile } = this.app.bajo
6
6
  let file
7
7
  let ext = '.ico'
@@ -10,7 +10,7 @@ async function handleFavicon (ctx) {
10
10
  ext = path.extname(file)
11
11
  }
12
12
  const me = this
13
- ctx.get(`/favicon${ext}`, async function (req, reply) {
13
+ this.instance.get(`/favicon${ext}`, async function (req, reply) {
14
14
  if (!file) return reply.code(404).send()
15
15
  reply.header('cache-control', 'max-age=86400')
16
16
  return await handleDownload.call(me, file, req, reply)
@@ -1,6 +1,6 @@
1
1
  import replyFrom from '@fastify/reply-from'
2
2
 
3
- async function handleForward (ctx) {
3
+ async function handleForward () {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  const me = this
6
6
 
@@ -13,9 +13,8 @@ async function handleForward (ctx) {
13
13
 
14
14
  const base = `http://${this.config.server.host}:${this.config.server.port}`
15
15
  const options = defaultsDeep({ base }, this.config.forwardOpts)
16
- await ctx.register(replyFrom, options)
17
-
18
- ctx.decorateReply('forwardTo', function (url, options = {}) {
16
+ this.instance.register(replyFrom, options)
17
+ this.instance.decorateReply('forwardTo', function (url, options = {}) {
19
18
  if (url.startsWith('http')) return this.redirectTo(url)
20
19
  this.from(me.routePath(url, options), {
21
20
  rewriteHeaders
@@ -1,4 +1,4 @@
1
- async function home () {
1
+ async function handleHome () {
2
2
  const { callHandler } = this.app.bajo
3
3
  const { defaultsDeep } = this.app.lib.aneka
4
4
  const { isString, pick } = this.app.lib._
@@ -14,4 +14,4 @@ async function home () {
14
14
  }
15
15
  }
16
16
 
17
- export default home
17
+ export default handleHome
@@ -0,0 +1,33 @@
1
+ export function writeHtml (req, reply, tpl, payload) {
2
+ const { getPluginFile } = this.app.bajo
3
+ const { fs } = this.app.lib
4
+ const { template } = this.app.lib._
5
+ reply.header('Content-Type', 'text/html')
6
+ reply.header('Content-Language', req.lang)
7
+ const file = getPluginFile(tpl)
8
+ const content = fs.readFileSync(file)
9
+ const compiled = template(content)
10
+ return compiled(payload)
11
+ }
12
+
13
+ export async function notFound (req, reply, err = {}) {
14
+ const { get } = this.app.lib._
15
+ const webApp = get(req, 'routeOptions.config.webApp')
16
+ reply.code(404)
17
+ if (webApp) {
18
+ const plugin = this.app[webApp]
19
+ const errorHandler = get(plugin, 'waibuFactory.errorHandler')
20
+ if (errorHandler) return await errorHandler.call(plugin, req, reply)
21
+ }
22
+ const text = req.t('notFound%s%s', req.t('route'), req.url)
23
+ return writeHtml.call(this, req, reply, `${this.ns}:/extend/bajoTemplate/template/400.html`, { text })
24
+ }
25
+
26
+ async function handleNotFound () {
27
+ const me = this
28
+ me.instance.setNotFoundHandler(async function (req, reply) {
29
+ return await notFound.call(me, req, reply)
30
+ })
31
+ }
32
+
33
+ export default handleNotFound
@@ -1,11 +1,15 @@
1
1
  import path from 'path'
2
2
 
3
- async function handleRedirect (ctx, options) {
3
+ export function redirect (reply, url, options = {}) {
4
+ if (url.startsWith('http') || path.isAbsolute(url)) reply.redirect(url)
5
+ else reply.redirect(this.routePath(url, options))
6
+ return reply
7
+ }
8
+
9
+ async function handleRedirect (options) {
4
10
  const me = this
5
- ctx.decorateReply('redirectTo', function (url, options = {}) {
6
- if (url.startsWith('http') || path.isAbsolute(url)) this.redirect(url)
7
- else this.redirect(me.routePath(url, options))
8
- return this
11
+ this.instance.decorateReply('redirectTo', function (url, options = {}) {
12
+ return redirect.call(me, this, url, options)
9
13
  })
10
14
  }
11
15
 
@@ -1,6 +1,6 @@
1
1
  // based on: https://github.com/NaturalIntelligence/fastify-xml-body-parser/blob/master/index.js
2
2
 
3
- async function xmlBodyParser (ctx, opts = {}) {
3
+ async function xmlBodyParser (opts = {}) {
4
4
  if (!this.app.bajoExtra) {
5
5
  this.log.warn('cantParseXmlBodyWithout%s', 'bajo-extra')
6
6
  return
@@ -48,7 +48,7 @@ async function xmlBodyParser (ctx, opts = {}) {
48
48
  }
49
49
  }
50
50
 
51
- ctx.addContentTypeParser(opts.contentTypes, contentParser)
51
+ this.instance.addContentTypeParser(opts.contentTypes, contentParser)
52
52
  }
53
53
 
54
54
  export default xmlBodyParser
@@ -1,5 +1,3 @@
1
- import home from './home.js'
2
-
3
1
  export async function collect (glob = 'boot.js', baseNs) {
4
2
  const { eachPlugins, importModule } = this.app.bajo
5
3
  const { orderBy, get } = this.app.lib._
@@ -24,26 +22,29 @@ export async function collect (glob = 'boot.js', baseNs) {
24
22
  return orderBy(mods, ['level'])
25
23
  }
26
24
 
27
- export async function boot () {
25
+ async function webApp () {
28
26
  const { runHook } = this.app.bajo
29
27
  const mods = await collect.call(this)
30
28
  await runHook(`${this.ns}:beforeAppBoot`)
29
+ // build routes
31
30
  for (const m of mods) {
32
31
  const disabled = this.app[m.ns].config.disabled
33
32
  if (Array.isArray(disabled) && disabled.length === 1 && ['*', 'all'].includes(disabled[0])) {
34
33
  this.log.warn('allRoutesConfigDisabled%s', m.ns)
35
34
  continue
36
35
  }
36
+ const plugin = this.app[m.ns]
37
37
  await runHook(`${this.ns}.${m.ns}:beforeAppBoot`)
38
38
  this.log.debug('bootApp%s', m.ns)
39
39
  await this.instance.register(async (ctx) => {
40
- const plugin = this.app[m.ns]
41
- plugin.instance = ctx
40
+ plugin.webAppCtx = ctx
41
+ plugin.webAppFactory = m
42
42
  await runHook(`${plugin.ns}:afterCreateContext`, ctx)
43
- await m.handler.call(plugin, ctx, m.prefix)
43
+ await m.handler.call(plugin, m.prefix)
44
44
  }, { prefix: m.prefix })
45
45
  await runHook(`${this.ns}.${m.ns}:afterAppBoot`)
46
46
  }
47
47
  await runHook(`${this.ns}:afterAppBoot`)
48
- await home.call(this)
49
48
  }
49
+
50
+ export default webApp
@@ -59,6 +59,10 @@ async function attachIntl (detector = [], req, reply) {
59
59
  }
60
60
  req.format = (value, type = 'auto', opts = {}) => {
61
61
  opts.lang = opts.lang ?? req.lang
62
+ const timeZone = get(req, 'site.setting.sumba.timeZone', this.app.bajo.config.intl.format.datetime.timeZone)
63
+ opts.datetime = opts.datetime ?? { timeZone }
64
+ opts.date = opts.date ?? { timeZone }
65
+ opts.time = opts.time ?? { timeZone }
62
66
  return this.app.bajo.format(value, type, opts)
63
67
  }
64
68
  }
@@ -1,10 +1,10 @@
1
1
  import compress from '@fastify/compress'
2
2
 
3
- async function handleCompress (ctx, options = {}) {
3
+ async function handleCompress (options = {}) {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  if (options === false) return this.log.warn('middlewareDisabled%s', 'compress')
6
6
  const opts = defaultsDeep(options, this.app.waibu.config.compress)
7
- await ctx.register(compress, opts)
7
+ await this.webAppCtx.register(compress, opts)
8
8
  }
9
9
 
10
10
  export default handleCompress
@@ -1,10 +1,10 @@
1
1
  import cors from '@fastify/cors'
2
2
 
3
- async function handleCors (ctx, options = {}) {
3
+ async function handleCors (options = {}) {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  if (options === false) return this.log.warn('middlewareDisabled%s', 'cors')
6
6
  const opts = defaultsDeep(options, this.app.waibu.config.cors)
7
- await ctx.register(cors, opts)
7
+ await this.webAppCtx.register(cors, opts)
8
8
  }
9
9
 
10
10
  export default handleCors
@@ -1,10 +1,10 @@
1
1
  import helmet from '@fastify/helmet'
2
2
 
3
- async function handleHelmet (ctx, options = {}) {
3
+ async function handleHelmet (options = {}) {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  if (options === false) return this.log.warn('middlewareDisabled%s', 'helmet')
6
6
  const opts = defaultsDeep(options, this.app.waibu.config.helmet)
7
- await ctx.register(helmet, opts)
7
+ await this.webAppCtx.register(helmet, opts)
8
8
  }
9
9
 
10
10
  export default handleHelmet
@@ -16,7 +16,7 @@ async function onFileHandler () {
16
16
  }
17
17
  }
18
18
 
19
- async function handleMultipartBody (ctx, options = {}) {
19
+ async function handleMultipartBody (options = {}) {
20
20
  const { importPkg } = this.app.bajo
21
21
  const { defaultsDeep, isSet } = this.app.lib.aneka
22
22
  const { isArray, map, trim, isPlainObject, isEmpty } = this.app.lib._
@@ -25,7 +25,7 @@ async function handleMultipartBody (ctx, options = {}) {
25
25
  const opts = defaultsDeep(options, this.app.waibu.config.multipart)
26
26
  const onFile = await onFileHandler.call(this)
27
27
  opts.onFile = onFile
28
- await ctx.register(multipart, opts)
28
+ await this.webAppCtx.register(multipart, opts)
29
29
 
30
30
  function normalizeValue (value) {
31
31
  if (!isSet(value)) return
@@ -45,7 +45,7 @@ async function handleMultipartBody (ctx, options = {}) {
45
45
  return value
46
46
  }
47
47
 
48
- ctx.addHook('preValidation', async function (req, reply) {
48
+ this.webAppCtx.addHook('preValidation', async function (req, reply) {
49
49
  if (req.isMultipart() && opts.attachFieldsToBody === true) {
50
50
  const body = Object.fromEntries(
51
51
  Object.keys(req.body || {}).map((key) => {
@@ -1,11 +1,11 @@
1
1
  import rateLimit from '@fastify/rate-limit'
2
2
 
3
- async function handleRateLimit (ctx, options = {}) {
3
+ async function handleRateLimit (options = {}) {
4
4
  const { cloneDeep } = this.app.lib._
5
5
  const { defaultsDeep } = this.app.lib.aneka
6
6
  if (options === false) return this.log.warn('middlewareDisabled%s', 'rateLimit')
7
7
  const opts = defaultsDeep(options, this.app.waibu.config.rateLimit)
8
- await ctx.register(rateLimit, cloneDeep(opts))
8
+ await this.webAppCtx.register(rateLimit, cloneDeep(opts))
9
9
  }
10
10
 
11
11
  export default handleRateLimit
@@ -1,9 +1,9 @@
1
1
  async function routeHook (ns) {
2
- const ctx = this.app[ns].instance
2
+ const webAppCtx = ns === 'waibu' ? this.app.waibu.instance : this.app[ns].webAppCtx
3
3
  const { runHook } = this.app.bajo
4
4
  const { hookTypes } = this.app.baseClass.Waibu
5
5
  for (const hook of hookTypes) {
6
- ctx.addHook(hook, async function (...args) {
6
+ webAppCtx.addHook(hook, async function (...args) {
7
7
  args.push(this)
8
8
  await runHook(`${ns}:${hook}`, ...args)
9
9
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Web Framework for Bajo",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-02-08
4
+
5
+ - [2.3.0] Simplify all common handler calls
6
+ - [2.3.0] All ```webApp``` now have it's assigned fastify context ```plugin.webAppCtx```
7
+ - [2.3.0] Simplify & unite error handler & not found handler
8
+ - [2.3.0] Add ```options.timeZone``` in ```req.format()```
9
+
3
10
  ## 2026-02-01
4
11
 
5
12
  - [2.2.0] Change query string token ```match``` to ```search``` for fulltext search
@@ -1,20 +0,0 @@
1
- async function errorHandler (ctx, extHandler) {
2
- const me = this
3
- ctx.setErrorHandler(async function (err, req, reply) {
4
- if (err.redirect) return reply.redirect(err.redirect)
5
- if (err.print) {
6
- reply.send(err.print)
7
- return
8
- }
9
- if (me.app.bajo.config.env !== 'prod' &&
10
- !['_notfound', '_redirect'].includes(err.message.toLowerCase())) console.error(err)
11
- if (extHandler) return await extHandler.call(me, err, req, reply, ctx)
12
- if (err.message === '_notFound' || err.statusCode === 404) {
13
- reply.code(err.statusCode)
14
- return
15
- }
16
- reply.code(err.statusCode ?? 500)
17
- })
18
- }
19
-
20
- export default errorHandler