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.3',
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 { path: p, ApiClass, swagger: routeSwagger } = item
616
- const pathParams = parsePathParams(p)
617
- const resolvedPath = p.startsWith('/') ? p : `/${p}`
741
+ const { ApiClass, swagger: routeSwagger } = item
742
+ const slots = item.slots || []
618
743
 
619
- if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
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
- 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()
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: 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}`,
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>.topbar form { display: none !important; }</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 { path: routePath, ApiClass, middleware: routeMiddleware = [] } of registry) {
755
- for (const methodName of HTTP_METHODS) {
756
- if (!definesMethod(ApiClass, methodName)) continue
757
- this.engine.route(methodName.toUpperCase(), routePath, (errOrRequest, maybeRequest) => {
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[methodName]
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fusion-framework",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
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",