fusion-framework 1.2.3 → 1.2.5
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.5',
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function isThenable(value) {
|
|
@@ -219,6 +219,128 @@ function resolveRoutePath(routePath, ApiClass) {
|
|
|
219
219
|
return native.resolveRoutePathJs(routePath, ApiClass.name)
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
function apiActionName(methodName) {
|
|
223
|
+
if (typeof native.apiActionNameJs === 'function') {
|
|
224
|
+
return native.apiActionNameJs(methodName)
|
|
225
|
+
}
|
|
226
|
+
let stem = methodName
|
|
227
|
+
if (methodName.endsWith('Action') && methodName.length > 6) stem = methodName.slice(0, -6)
|
|
228
|
+
else if (methodName.endsWith('ACTION') && methodName.length > 6) stem = methodName.slice(0, -6)
|
|
229
|
+
return stem.toLowerCase()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function resolveMethodRoutePath(template, className, methodName) {
|
|
233
|
+
if (typeof native.resolveMethodRoutePathJs === 'function') {
|
|
234
|
+
return native.resolveMethodRoutePathJs(template, className, methodName)
|
|
235
|
+
}
|
|
236
|
+
return resolveRoutePath(template, { name: className }).replace(
|
|
237
|
+
/\[action\]/g,
|
|
238
|
+
apiActionName(methodName),
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function joinRoutePaths(base, segment) {
|
|
243
|
+
const left = String(base || '').replace(/\/+$/, '')
|
|
244
|
+
const right = String(segment || '').replace(/^\/+|\/+$/g, '')
|
|
245
|
+
if (!right) return left || '/'
|
|
246
|
+
if (!left) return `/${right}`
|
|
247
|
+
return `${left}/${right}`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function resolveHandlerRoute(classBasePath, template, className, methodName) {
|
|
251
|
+
if (typeof native.resolveHandlerRouteJs === 'function') {
|
|
252
|
+
return native.resolveHandlerRouteJs(classBasePath, template, className, methodName)
|
|
253
|
+
}
|
|
254
|
+
const resolved = resolveMethodRoutePath(template, className, methodName)
|
|
255
|
+
if (String(template).startsWith('/')) {
|
|
256
|
+
return joinRoutePaths('', resolved.replace(/^\/+/, ''))
|
|
257
|
+
}
|
|
258
|
+
return joinRoutePaths(classBasePath, resolved)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function httpRoute(method, route, options = {}) {
|
|
262
|
+
// tags unset → inherit class route tags; tags: [] clears; tags: [...] overrides.
|
|
263
|
+
const tagsSet = Object.prototype.hasOwnProperty.call(options, 'tags')
|
|
264
|
+
const meta = {
|
|
265
|
+
method: String(method).toLowerCase(),
|
|
266
|
+
template: route,
|
|
267
|
+
tagsSet,
|
|
268
|
+
tags: tagsSet ? (Array.isArray(options.tags) ? options.tags : []) : null,
|
|
269
|
+
desc: options.desc ?? null,
|
|
270
|
+
title: options.title ?? null,
|
|
271
|
+
deprecated: !!options.deprecated,
|
|
272
|
+
}
|
|
273
|
+
function wrap(fn) {
|
|
274
|
+
fn.__fusionHttpRoute = meta
|
|
275
|
+
return fn
|
|
276
|
+
}
|
|
277
|
+
return wrap
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function mergeSwagger(methodSwagger, classSwagger) {
|
|
281
|
+
return {
|
|
282
|
+
tags: methodSwagger.tagsSet ? methodSwagger.tags : classSwagger.tags,
|
|
283
|
+
description: methodSwagger.desc ?? classSwagger.description,
|
|
284
|
+
title: methodSwagger.title ?? classSwagger.title,
|
|
285
|
+
deprecated: !!(methodSwagger.deprecated || classSwagger.deprecated),
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function httpGet(route, options = {}) {
|
|
290
|
+
return httpRoute('get', route, options)
|
|
291
|
+
}
|
|
292
|
+
function httpPost(route, options = {}) {
|
|
293
|
+
return httpRoute('post', route, options)
|
|
294
|
+
}
|
|
295
|
+
function httpPut(route, options = {}) {
|
|
296
|
+
return httpRoute('put', route, options)
|
|
297
|
+
}
|
|
298
|
+
function httpPatch(route, options = {}) {
|
|
299
|
+
return httpRoute('patch', route, options)
|
|
300
|
+
}
|
|
301
|
+
function httpDelete(route, options = {}) {
|
|
302
|
+
return httpRoute('delete', route, options)
|
|
303
|
+
}
|
|
304
|
+
function httpHead(route, options = {}) {
|
|
305
|
+
return httpRoute('head', route, options)
|
|
306
|
+
}
|
|
307
|
+
function httpOptions(route, options = {}) {
|
|
308
|
+
return httpRoute('options', route, options)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function collectRouteSlots(ApiClass, classBasePath, classSwagger) {
|
|
312
|
+
const slots = []
|
|
313
|
+
const custom = new Set()
|
|
314
|
+
const className = ApiClass.name
|
|
315
|
+
|
|
316
|
+
for (const key of Object.getOwnPropertyNames(ApiClass.prototype)) {
|
|
317
|
+
if (key === 'constructor' || key.startsWith('_')) continue
|
|
318
|
+
const fn = ApiClass.prototype[key]
|
|
319
|
+
if (typeof fn !== 'function' || !fn.__fusionHttpRoute) continue
|
|
320
|
+
custom.add(key)
|
|
321
|
+
const meta = fn.__fusionHttpRoute
|
|
322
|
+
slots.push({
|
|
323
|
+
path: resolveHandlerRoute(classBasePath, meta.template, className, key),
|
|
324
|
+
httpMethod: meta.method,
|
|
325
|
+
handlerMethod: key,
|
|
326
|
+
swagger: mergeSwagger(meta, classSwagger),
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (const methodName of HTTP_METHODS) {
|
|
331
|
+
if (custom.has(methodName)) continue
|
|
332
|
+
if (!definesMethod(ApiClass, methodName)) continue
|
|
333
|
+
slots.push({
|
|
334
|
+
path: classBasePath,
|
|
335
|
+
httpMethod: methodName,
|
|
336
|
+
handlerMethod: methodName,
|
|
337
|
+
swagger: classSwagger,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return slots
|
|
342
|
+
}
|
|
343
|
+
|
|
222
344
|
function emptyRequest() {
|
|
223
345
|
return {
|
|
224
346
|
method: '',
|
|
@@ -372,17 +494,21 @@ function router(routePath, options = {}) {
|
|
|
372
494
|
)
|
|
373
495
|
}
|
|
374
496
|
|
|
497
|
+
const classSwagger = {
|
|
498
|
+
tags: Array.isArray(options.tags) ? options.tags : [],
|
|
499
|
+
description: options.desc ?? null,
|
|
500
|
+
title: options.title ?? null,
|
|
501
|
+
deprecated: !!options.deprecated,
|
|
502
|
+
}
|
|
503
|
+
|
|
375
504
|
registry.push({
|
|
376
505
|
path: resolved,
|
|
506
|
+
classBasePath: resolved,
|
|
377
507
|
ApiClass,
|
|
378
508
|
middleware: routeMiddleware,
|
|
379
|
-
swagger:
|
|
380
|
-
tags: Array.isArray(options.tags) ? options.tags : [],
|
|
381
|
-
description: options.desc ?? null,
|
|
382
|
-
title: options.title ?? null,
|
|
383
|
-
deprecated: !!options.deprecated,
|
|
384
|
-
},
|
|
509
|
+
swagger: classSwagger,
|
|
385
510
|
version_prefix: v,
|
|
511
|
+
slots: collectRouteSlots(ApiClass, resolved, classSwagger),
|
|
386
512
|
})
|
|
387
513
|
return ApiClass
|
|
388
514
|
}
|
|
@@ -612,18 +738,18 @@ function fillOpenApiPaths(openapi, versionFilter = null) {
|
|
|
612
738
|
|
|
613
739
|
for (const item of registry) {
|
|
614
740
|
if (!routeMatchesVersion(item, versionFilter)) continue
|
|
615
|
-
const {
|
|
616
|
-
const
|
|
617
|
-
const resolvedPath = p.startsWith('/') ? p : `/${p}`
|
|
741
|
+
const { ApiClass, swagger: routeSwagger } = item
|
|
742
|
+
const slots = item.slots || []
|
|
618
743
|
|
|
619
|
-
|
|
744
|
+
for (const slot of slots) {
|
|
745
|
+
const pathParams = parsePathParams(slot.path)
|
|
746
|
+
const resolvedPath = slot.path.startsWith('/') ? slot.path : `/${slot.path}`
|
|
747
|
+
const routeSwaggerEntry = slot.swagger || routeSwagger
|
|
620
748
|
|
|
621
|
-
|
|
622
|
-
if (!definesMethod(ApiClass, methodName)) continue
|
|
623
|
-
|
|
624
|
-
const methodUpper = String(methodName).toUpperCase()
|
|
625
|
-
const methodLower = String(methodName).toLowerCase()
|
|
749
|
+
if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
|
|
626
750
|
|
|
751
|
+
const methodLower = String(slot.httpMethod).toLowerCase()
|
|
752
|
+
const methodUpper = methodLower.toUpperCase()
|
|
627
753
|
const params = pathParams.map((name) => ({
|
|
628
754
|
name,
|
|
629
755
|
in: 'path',
|
|
@@ -632,11 +758,11 @@ function fillOpenApiPaths(openapi, versionFilter = null) {
|
|
|
632
758
|
}))
|
|
633
759
|
|
|
634
760
|
openapi.paths[resolvedPath][methodLower] = {
|
|
635
|
-
tags:
|
|
636
|
-
summary:
|
|
637
|
-
description:
|
|
638
|
-
deprecated: !!
|
|
639
|
-
operationId: `${ApiClass.name}_${
|
|
761
|
+
tags: routeSwaggerEntry?.tags?.length ? routeSwaggerEntry.tags : [],
|
|
762
|
+
summary: routeSwaggerEntry?.title ?? `${ApiClass.name}.${slot.handlerMethod}`,
|
|
763
|
+
description: routeSwaggerEntry?.description ?? '',
|
|
764
|
+
deprecated: !!routeSwaggerEntry?.deprecated,
|
|
765
|
+
operationId: `${ApiClass.name}_${slot.handlerMethod}`,
|
|
640
766
|
parameters: params,
|
|
641
767
|
responses: { 200: { description: 'OK' } },
|
|
642
768
|
}
|
|
@@ -691,7 +817,12 @@ function swaggerUiHtml(swagger, openapiUrl, primaryName = null) {
|
|
|
691
817
|
const showUrlInput = swagger.navbar?.showUrlInput !== false
|
|
692
818
|
const hideUrlCss =
|
|
693
819
|
navbarEnabled && !showUrlInput
|
|
694
|
-
? `<style
|
|
820
|
+
? `<style>
|
|
821
|
+
.swagger-ui .topbar .download-url-wrapper input[type=text],
|
|
822
|
+
.swagger-ui .topbar .download-url-wrapper .download-url-button {
|
|
823
|
+
display: none !important;
|
|
824
|
+
}
|
|
825
|
+
</style>`
|
|
695
826
|
: ''
|
|
696
827
|
const standaloneScript = navbarEnabled
|
|
697
828
|
? `<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>`
|
|
@@ -751,16 +882,16 @@ class FusionApp {
|
|
|
751
882
|
if (this.mounted) return
|
|
752
883
|
activeGlobalMiddleware = [...this._middleware]
|
|
753
884
|
|
|
754
|
-
for (const {
|
|
755
|
-
for (const
|
|
756
|
-
|
|
757
|
-
this.engine.route(
|
|
885
|
+
for (const { ApiClass, middleware: routeMiddleware = [], slots = [] } of registry) {
|
|
886
|
+
for (const slot of slots) {
|
|
887
|
+
const handlerMethod = slot.handlerMethod
|
|
888
|
+
this.engine.route(String(slot.httpMethod).toUpperCase(), slot.path, (errOrRequest, maybeRequest) => {
|
|
758
889
|
const request = nativeRequestArg(errOrRequest, maybeRequest)
|
|
759
890
|
const chain = [...activeGlobalMiddleware, ...routeMiddleware]
|
|
760
891
|
const handler = async (req) => {
|
|
761
892
|
try {
|
|
762
893
|
const instance = new ApiClass(req || emptyRequest())
|
|
763
|
-
const fn = instance[
|
|
894
|
+
const fn = instance[handlerMethod]
|
|
764
895
|
return await Promise.resolve(fn.call(instance))
|
|
765
896
|
} catch (err) {
|
|
766
897
|
if (err instanceof HTTPException) return err.toResponse()
|
|
@@ -852,8 +983,17 @@ module.exports = {
|
|
|
852
983
|
HTTPException,
|
|
853
984
|
router,
|
|
854
985
|
route,
|
|
986
|
+
httpGet,
|
|
987
|
+
httpPost,
|
|
988
|
+
httpPut,
|
|
989
|
+
httpPatch,
|
|
990
|
+
httpDelete,
|
|
991
|
+
httpHead,
|
|
992
|
+
httpOptions,
|
|
855
993
|
apiResourceName,
|
|
856
994
|
resolveRoutePath,
|
|
995
|
+
resolveHandlerRoute,
|
|
996
|
+
apiActionName,
|
|
857
997
|
configure,
|
|
858
998
|
getSettings,
|
|
859
999
|
settings,
|