fusion-framework 1.2.0 → 1.2.2

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.
Binary file
Binary file
Binary file
Binary file
package/index.js CHANGED
@@ -134,30 +134,43 @@ header.fingerprint = () =>
134
134
  : {
135
135
  'X-Powered-By': 'Fusion Framework',
136
136
  'X-Framework': 'Fusion',
137
- 'X-Fusion-Version': '1.2.0',
137
+ ['X-Fusion-Version']: '1.2.2',
138
138
  }
139
139
 
140
+ function isThenable(value) {
141
+ return value != null && typeof value.then === 'function'
142
+ }
143
+
144
+ async function awaitMaybe(value) {
145
+ // Nested thenables (middleware wrapping async handlers) — same idea as Python
146
+ // awaiting coroutines before treating the value as an HTTP body.
147
+ let current = value
148
+ for (let i = 0; i < 8 && isThenable(current); i++) {
149
+ current = await current
150
+ }
151
+ return current
152
+ }
153
+
154
+ function mergeResponseHeaders(result, extra) {
155
+ if (!result || typeof result !== 'object') {
156
+ return { status: 200, body: result, headers: { ...extra } }
157
+ }
158
+ const headers = { ...extra, ...(result.headers || {}) }
159
+ return { ...result, headers }
160
+ }
161
+
140
162
  function frameworkHeaders() {
141
163
  const extra = header.fingerprint()
142
- return (request, callNext) => {
143
- const result = callNext(request)
144
- const apply = (value) => {
145
- if (value && typeof value.then === 'function') {
146
- return value.then(apply)
147
- }
148
- if (!value || typeof value !== 'object') {
149
- return { status: 200, body: value, headers: { ...extra } }
150
- }
151
- const headers = { ...extra, ...(value.headers || {}) }
152
- return { ...value, headers }
153
- }
154
- return apply(result)
164
+ // Async so Promises from callNext are never stuffed into `body` as objects.
165
+ return async (request, callNext) => {
166
+ const result = await awaitMaybe(callNext(request))
167
+ return mergeResponseHeaders(result, extra)
155
168
  }
156
169
  }
157
170
 
158
171
  class FusionBaseApi {
159
172
  constructor(request) {
160
- this.request = request
173
+ this.request = request && typeof request === 'object' ? request : emptyRequest()
161
174
  }
162
175
 
163
176
  get method() {
@@ -206,7 +219,39 @@ function resolveRoutePath(routePath, ApiClass) {
206
219
  return native.resolveRoutePathJs(routePath, ApiClass.name)
207
220
  }
208
221
 
222
+ function emptyRequest() {
223
+ return {
224
+ method: '',
225
+ path: '',
226
+ body: '',
227
+ headers: {},
228
+ params: {},
229
+ query: {},
230
+ state: {},
231
+ }
232
+ }
233
+
234
+ /**
235
+ * N-API threadsafe callbacks may be `(request)` (Fatal) or `(err, request)` (CalleeHandled).
236
+ * Always return a real request object so middleware never sees `null`.
237
+ */
238
+ function nativeRequestArg(errOrRequest, maybeRequest) {
239
+ if (maybeRequest != null && typeof maybeRequest === 'object') {
240
+ return maybeRequest
241
+ }
242
+ if (errOrRequest instanceof Error) {
243
+ throw errOrRequest
244
+ }
245
+ if (errOrRequest != null && typeof errOrRequest === 'object') {
246
+ return errOrRequest
247
+ }
248
+ return emptyRequest()
249
+ }
250
+
209
251
  function ensureState(request) {
252
+ if (!request || typeof request !== 'object') {
253
+ return {}
254
+ }
210
255
  if (!request.state || typeof request.state !== 'object') {
211
256
  request.state = {}
212
257
  }
@@ -219,18 +264,14 @@ function isResponse(value) {
219
264
 
220
265
  async function runMiddlewareChain(request, middlewares, handler) {
221
266
  ensureState(request)
222
- let index = 0
223
267
 
224
268
  async function dispatch(i, req) {
225
269
  if (i >= middlewares.length) {
226
- return await handler(req)
270
+ return await awaitMaybe(handler(req))
227
271
  }
228
272
  const middleware = middlewares[i]
229
273
  const callNext = (nextReq) => dispatch(i + 1, nextReq)
230
- let result = middleware(req, callNext)
231
- if (result && typeof result.then === 'function') {
232
- result = await result
233
- }
274
+ const result = await awaitMaybe(middleware(req, callNext))
234
275
  if (isResponse(result)) return result
235
276
  return result
236
277
  }
@@ -595,11 +636,12 @@ class FusionApp {
595
636
  for (const { path: routePath, ApiClass, middleware: routeMiddleware = [] } of registry) {
596
637
  for (const methodName of HTTP_METHODS) {
597
638
  if (!definesMethod(ApiClass, methodName)) continue
598
- this.engine.route(methodName.toUpperCase(), routePath, async (request) => {
639
+ this.engine.route(methodName.toUpperCase(), routePath, (errOrRequest, maybeRequest) => {
640
+ const request = nativeRequestArg(errOrRequest, maybeRequest)
599
641
  const chain = [...activeGlobalMiddleware, ...routeMiddleware]
600
642
  const handler = async (req) => {
601
643
  try {
602
- const instance = new ApiClass(req)
644
+ const instance = new ApiClass(req || emptyRequest())
603
645
  const fn = instance[methodName]
604
646
  return await Promise.resolve(fn.call(instance))
605
647
  } catch (err) {
@@ -664,12 +706,16 @@ class FusionApp {
664
706
  }
665
707
  }
666
708
 
667
- this.engine.route('GET', `${prefix}/openapi.json`, async () => openapi)
668
- this.engine.route('GET', prefix, async () => ({
709
+ const uiEnvelope = () => ({
669
710
  status: 200,
670
711
  body: swaggerUiHtml(swagger, `${prefix}/openapi.json`),
671
712
  headers: { 'content-type': 'text/html' },
672
- }))
713
+ })
714
+ this.engine.route('GET', `${prefix}/openapi.json`, () => openapi)
715
+ this.engine.route('GET', prefix, uiEnvelope)
716
+ if (prefix !== '/') {
717
+ this.engine.route('GET', `${prefix}/`, uiEnvelope)
718
+ }
673
719
  }
674
720
 
675
721
  this.mounted = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fusion-framework",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Class-based HTTP framework for Node.js, powered by a shared Rust core via N-API",
5
5
  "keywords": [
6
6
  "http",