fusion-framework 1.2.1 → 1.2.3
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,7 +134,7 @@ header.fingerprint = () =>
|
|
|
134
134
|
: {
|
|
135
135
|
'X-Powered-By': 'Fusion Framework',
|
|
136
136
|
'X-Framework': 'Fusion',
|
|
137
|
-
'X-Fusion-Version': '1.2.
|
|
137
|
+
['X-Fusion-Version']: '1.2.3',
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function isThenable(value) {
|
|
@@ -170,7 +170,7 @@ function frameworkHeaders() {
|
|
|
170
170
|
|
|
171
171
|
class FusionBaseApi {
|
|
172
172
|
constructor(request) {
|
|
173
|
-
this.request = request
|
|
173
|
+
this.request = request && typeof request === 'object' ? request : emptyRequest()
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
get method() {
|
|
@@ -219,7 +219,39 @@ function resolveRoutePath(routePath, ApiClass) {
|
|
|
219
219
|
return native.resolveRoutePathJs(routePath, ApiClass.name)
|
|
220
220
|
}
|
|
221
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
|
+
|
|
222
251
|
function ensureState(request) {
|
|
252
|
+
if (!request || typeof request !== 'object') {
|
|
253
|
+
return {}
|
|
254
|
+
}
|
|
223
255
|
if (!request.state || typeof request.state !== 'object') {
|
|
224
256
|
request.state = {}
|
|
225
257
|
}
|
|
@@ -455,7 +487,9 @@ function readSwaggerSettings() {
|
|
|
455
487
|
const navbar = {
|
|
456
488
|
enabled: truthyEnabled(navbarRaw.enabled, true),
|
|
457
489
|
showUrlInput: truthyEnabled(navbarRaw.showUrlInput, true),
|
|
490
|
+
showUrlInputSet: Object.prototype.hasOwnProperty.call(navbarRaw, 'showUrlInput'),
|
|
458
491
|
urls: Array.isArray(navbarRaw.urls) ? navbarRaw.urls : null,
|
|
492
|
+
urlsSet: Array.isArray(navbarRaw.urls),
|
|
459
493
|
}
|
|
460
494
|
|
|
461
495
|
const ui = {
|
|
@@ -500,6 +534,60 @@ function readSwaggerSettings() {
|
|
|
500
534
|
}
|
|
501
535
|
}
|
|
502
536
|
|
|
537
|
+
const UNVERSIONED_SWAGGER_NAME = 'default'
|
|
538
|
+
|
|
539
|
+
function normalizeVersionLabel(value) {
|
|
540
|
+
return String(value || '')
|
|
541
|
+
.trim()
|
|
542
|
+
.replace(/^\/+|\/+$/g, '')
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function collectRouteVersions() {
|
|
546
|
+
const versions = []
|
|
547
|
+
let hasUnversioned = false
|
|
548
|
+
for (const item of registry) {
|
|
549
|
+
const version = normalizeVersionLabel(item.version_prefix)
|
|
550
|
+
if (!version) {
|
|
551
|
+
hasUnversioned = true
|
|
552
|
+
continue
|
|
553
|
+
}
|
|
554
|
+
if (!versions.includes(version)) versions.push(version)
|
|
555
|
+
}
|
|
556
|
+
return { versions, hasUnversioned }
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function swaggerVersionUrls(prefix) {
|
|
560
|
+
const { versions, hasUnversioned } = collectRouteVersions()
|
|
561
|
+
const urls = versions.map((label) => ({
|
|
562
|
+
url: `${prefix}/${label}/openapi.json`,
|
|
563
|
+
name: label,
|
|
564
|
+
}))
|
|
565
|
+
if (hasUnversioned && urls.length) {
|
|
566
|
+
urls.push({
|
|
567
|
+
url: `${prefix}/${UNVERSIONED_SWAGGER_NAME}/openapi.json`,
|
|
568
|
+
name: UNVERSIONED_SWAGGER_NAME,
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
return urls
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function applyVersionNavbar(swagger) {
|
|
575
|
+
const autoUrls = swaggerVersionUrls(swagger.path)
|
|
576
|
+
if (!swagger.navbar.urlsSet && autoUrls.length) {
|
|
577
|
+
swagger.navbar.urls = autoUrls
|
|
578
|
+
if (!swagger.navbar.showUrlInputSet) swagger.navbar.showUrlInput = false
|
|
579
|
+
}
|
|
580
|
+
return autoUrls.map((item) => item.name)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function routeMatchesVersion(item, filter) {
|
|
584
|
+
const version = normalizeVersionLabel(item.version_prefix)
|
|
585
|
+
if (filter == null) return true
|
|
586
|
+
const label = normalizeVersionLabel(filter)
|
|
587
|
+
if (!label || label.toLowerCase() === UNVERSIONED_SWAGGER_NAME) return !version
|
|
588
|
+
return version.toLowerCase() === label.toLowerCase()
|
|
589
|
+
}
|
|
590
|
+
|
|
503
591
|
function applySwaggerOpenApi(openapi, swagger) {
|
|
504
592
|
openapi.info = { ...asObject(openapi.info), ...swagger.info }
|
|
505
593
|
if (swagger.servers?.length) openapi.servers = swagger.servers
|
|
@@ -514,7 +602,66 @@ function applySwaggerOpenApi(openapi, swagger) {
|
|
|
514
602
|
return openapi
|
|
515
603
|
}
|
|
516
604
|
|
|
517
|
-
function
|
|
605
|
+
function fillOpenApiPaths(openapi, versionFilter = null) {
|
|
606
|
+
const parsePathParams = (pattern) => {
|
|
607
|
+
return String(pattern)
|
|
608
|
+
.split('/')
|
|
609
|
+
.filter((seg) => (seg.startsWith('{') && seg.endsWith('}')) || (seg.startsWith('[') && seg.endsWith(']')))
|
|
610
|
+
.map((seg) => seg.slice(1, -1))
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for (const item of registry) {
|
|
614
|
+
if (!routeMatchesVersion(item, versionFilter)) continue
|
|
615
|
+
const { path: p, ApiClass, swagger: routeSwagger } = item
|
|
616
|
+
const pathParams = parsePathParams(p)
|
|
617
|
+
const resolvedPath = p.startsWith('/') ? p : `/${p}`
|
|
618
|
+
|
|
619
|
+
if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
|
|
620
|
+
|
|
621
|
+
for (const methodName of HTTP_METHODS) {
|
|
622
|
+
if (!definesMethod(ApiClass, methodName)) continue
|
|
623
|
+
|
|
624
|
+
const methodUpper = String(methodName).toUpperCase()
|
|
625
|
+
const methodLower = String(methodName).toLowerCase()
|
|
626
|
+
|
|
627
|
+
const params = pathParams.map((name) => ({
|
|
628
|
+
name,
|
|
629
|
+
in: 'path',
|
|
630
|
+
required: true,
|
|
631
|
+
schema: { type: 'string' },
|
|
632
|
+
}))
|
|
633
|
+
|
|
634
|
+
openapi.paths[resolvedPath][methodLower] = {
|
|
635
|
+
tags: routeSwagger?.tags?.length ? routeSwagger.tags : [],
|
|
636
|
+
summary: routeSwagger?.title ?? `${ApiClass.name}.${methodUpper}`,
|
|
637
|
+
description: routeSwagger?.description ?? '',
|
|
638
|
+
deprecated: !!routeSwagger?.deprecated,
|
|
639
|
+
operationId: `${ApiClass.name}_${methodLower}`,
|
|
640
|
+
parameters: params,
|
|
641
|
+
responses: { 200: { description: 'OK' } },
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return openapi
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function buildOpenApi(swagger, version = null) {
|
|
649
|
+
const openapi = applySwaggerOpenApi(
|
|
650
|
+
{
|
|
651
|
+
openapi: '3.0.3',
|
|
652
|
+
info: { ...swagger.info },
|
|
653
|
+
paths: {},
|
|
654
|
+
},
|
|
655
|
+
swagger,
|
|
656
|
+
)
|
|
657
|
+
const label = normalizeVersionLabel(version)
|
|
658
|
+
if (label && label !== UNVERSIONED_SWAGGER_NAME) {
|
|
659
|
+
openapi.info = { ...openapi.info, version: label }
|
|
660
|
+
}
|
|
661
|
+
return fillOpenApiPaths(openapi, version)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function swaggerUiHtml(swagger, openapiUrl, primaryName = null) {
|
|
518
665
|
const uiOpts = { ...swagger.ui }
|
|
519
666
|
delete uiOpts.presets
|
|
520
667
|
delete uiOpts.plugins
|
|
@@ -523,9 +670,12 @@ function swaggerUiHtml(swagger, openapiUrl) {
|
|
|
523
670
|
if (swagger.navbar?.urls?.length) {
|
|
524
671
|
delete uiOpts.url
|
|
525
672
|
uiOpts.urls = swagger.navbar.urls
|
|
673
|
+
const name = primaryName || swagger.navbar.urls[0]?.name
|
|
674
|
+
if (name) uiOpts['urls.primaryName'] = name
|
|
526
675
|
} else {
|
|
527
676
|
uiOpts.url = openapiUrl
|
|
528
677
|
delete uiOpts.urls
|
|
678
|
+
delete uiOpts['urls.primaryName']
|
|
529
679
|
}
|
|
530
680
|
uiOpts.dom_id = '#swagger-ui'
|
|
531
681
|
|
|
@@ -541,7 +691,7 @@ function swaggerUiHtml(swagger, openapiUrl) {
|
|
|
541
691
|
const showUrlInput = swagger.navbar?.showUrlInput !== false
|
|
542
692
|
const hideUrlCss =
|
|
543
693
|
navbarEnabled && !showUrlInput
|
|
544
|
-
? `<style>.topbar
|
|
694
|
+
? `<style>.topbar form { display: none !important; }</style>`
|
|
545
695
|
: ''
|
|
546
696
|
const standaloneScript = navbarEnabled
|
|
547
697
|
? `<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>`
|
|
@@ -604,11 +754,12 @@ class FusionApp {
|
|
|
604
754
|
for (const { path: routePath, ApiClass, middleware: routeMiddleware = [] } of registry) {
|
|
605
755
|
for (const methodName of HTTP_METHODS) {
|
|
606
756
|
if (!definesMethod(ApiClass, methodName)) continue
|
|
607
|
-
this.engine.route(methodName.toUpperCase(), routePath,
|
|
757
|
+
this.engine.route(methodName.toUpperCase(), routePath, (errOrRequest, maybeRequest) => {
|
|
758
|
+
const request = nativeRequestArg(errOrRequest, maybeRequest)
|
|
608
759
|
const chain = [...activeGlobalMiddleware, ...routeMiddleware]
|
|
609
760
|
const handler = async (req) => {
|
|
610
761
|
try {
|
|
611
|
-
const instance = new ApiClass(req)
|
|
762
|
+
const instance = new ApiClass(req || emptyRequest())
|
|
612
763
|
const fn = instance[methodName]
|
|
613
764
|
return await Promise.resolve(fn.call(instance))
|
|
614
765
|
} catch (err) {
|
|
@@ -624,61 +775,27 @@ class FusionApp {
|
|
|
624
775
|
const swagger = readSwaggerSettings()
|
|
625
776
|
if (swagger.enabled) {
|
|
626
777
|
const prefix = swagger.path
|
|
778
|
+
const labels = applyVersionNavbar(swagger)
|
|
779
|
+
const combined = buildOpenApi(swagger)
|
|
627
780
|
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
},
|
|
634
|
-
swagger,
|
|
635
|
-
)
|
|
781
|
+
const htmlEnvelope = (primaryName = null) => ({
|
|
782
|
+
status: 200,
|
|
783
|
+
body: swaggerUiHtml(swagger, `${prefix}/openapi.json`, primaryName),
|
|
784
|
+
headers: { 'content-type': 'text/html' },
|
|
785
|
+
})
|
|
636
786
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
.map((seg) => seg.slice(1, -1))
|
|
787
|
+
this.engine.route('GET', `${prefix}/openapi.json`, () => combined)
|
|
788
|
+
this.engine.route('GET', prefix, () => htmlEnvelope())
|
|
789
|
+
if (prefix !== '/') {
|
|
790
|
+
this.engine.route('GET', `${prefix}/`, () => htmlEnvelope())
|
|
642
791
|
}
|
|
643
792
|
|
|
644
|
-
for (const
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
|
|
650
|
-
|
|
651
|
-
for (const methodName of HTTP_METHODS) {
|
|
652
|
-
if (!definesMethod(ApiClass, methodName)) continue
|
|
653
|
-
|
|
654
|
-
const methodUpper = String(methodName).toUpperCase()
|
|
655
|
-
const methodLower = String(methodName).toLowerCase()
|
|
656
|
-
|
|
657
|
-
const params = pathParams.map((name) => ({
|
|
658
|
-
name,
|
|
659
|
-
in: 'path',
|
|
660
|
-
required: true,
|
|
661
|
-
schema: { type: 'string' },
|
|
662
|
-
}))
|
|
663
|
-
|
|
664
|
-
openapi.paths[resolvedPath][methodLower] = {
|
|
665
|
-
tags: routeSwagger?.tags?.length ? routeSwagger.tags : [],
|
|
666
|
-
summary: routeSwagger?.title ?? `${ApiClass.name}.${methodUpper}`,
|
|
667
|
-
description: routeSwagger?.description ?? '',
|
|
668
|
-
deprecated: !!routeSwagger?.deprecated,
|
|
669
|
-
operationId: `${ApiClass.name}_${methodLower}`,
|
|
670
|
-
parameters: params,
|
|
671
|
-
responses: { '200': { description: 'OK' } },
|
|
672
|
-
}
|
|
673
|
-
}
|
|
793
|
+
for (const label of labels) {
|
|
794
|
+
const spec = buildOpenApi(swagger, label)
|
|
795
|
+
this.engine.route('GET', `${prefix}/${label}/openapi.json`, () => spec)
|
|
796
|
+
this.engine.route('GET', `${prefix}/${label}`, () => htmlEnvelope(label))
|
|
797
|
+
this.engine.route('GET', `${prefix}/${label}/`, () => htmlEnvelope(label))
|
|
674
798
|
}
|
|
675
|
-
|
|
676
|
-
this.engine.route('GET', `${prefix}/openapi.json`, async () => openapi)
|
|
677
|
-
this.engine.route('GET', prefix, async () => ({
|
|
678
|
-
status: 200,
|
|
679
|
-
body: swaggerUiHtml(swagger, `${prefix}/openapi.json`),
|
|
680
|
-
headers: { 'content-type': 'text/html' },
|
|
681
|
-
}))
|
|
682
799
|
}
|
|
683
800
|
|
|
684
801
|
this.mounted = true
|