fusion-framework 1.2.3 → 1.2.4

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.4',
138
138
  }
139
139
 
140
140
  function isThenable(value) {
@@ -219,6 +219,121 @@ 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
+ const meta = {
263
+ method: String(method).toLowerCase(),
264
+ template: route,
265
+ tags: Array.isArray(options.tags) ? options.tags : [],
266
+ desc: options.desc ?? null,
267
+ title: options.title ?? null,
268
+ deprecated: !!options.deprecated,
269
+ }
270
+ function wrap(fn) {
271
+ fn.__fusionHttpRoute = meta
272
+ return fn
273
+ }
274
+ return wrap
275
+ }
276
+
277
+ function httpGet(route, options = {}) {
278
+ return httpRoute('get', route, options)
279
+ }
280
+ function httpPost(route, options = {}) {
281
+ return httpRoute('post', route, options)
282
+ }
283
+ function httpPut(route, options = {}) {
284
+ return httpRoute('put', route, options)
285
+ }
286
+ function httpPatch(route, options = {}) {
287
+ return httpRoute('patch', route, options)
288
+ }
289
+ function httpDelete(route, options = {}) {
290
+ return httpRoute('delete', route, options)
291
+ }
292
+ function httpHead(route, options = {}) {
293
+ return httpRoute('head', route, options)
294
+ }
295
+ function httpOptions(route, options = {}) {
296
+ return httpRoute('options', route, options)
297
+ }
298
+
299
+ function collectRouteSlots(ApiClass, classBasePath, classSwagger) {
300
+ const slots = []
301
+ const custom = new Set()
302
+ const className = ApiClass.name
303
+
304
+ for (const key of Object.getOwnPropertyNames(ApiClass.prototype)) {
305
+ if (key === 'constructor' || key.startsWith('_')) continue
306
+ const fn = ApiClass.prototype[key]
307
+ if (typeof fn !== 'function' || !fn.__fusionHttpRoute) continue
308
+ custom.add(key)
309
+ const meta = fn.__fusionHttpRoute
310
+ slots.push({
311
+ path: resolveHandlerRoute(classBasePath, meta.template, className, key),
312
+ httpMethod: meta.method,
313
+ handlerMethod: key,
314
+ swagger: {
315
+ tags: meta.tags,
316
+ description: meta.desc,
317
+ title: meta.title,
318
+ deprecated: meta.deprecated,
319
+ },
320
+ })
321
+ }
322
+
323
+ for (const methodName of HTTP_METHODS) {
324
+ if (custom.has(methodName)) continue
325
+ if (!definesMethod(ApiClass, methodName)) continue
326
+ slots.push({
327
+ path: classBasePath,
328
+ httpMethod: methodName,
329
+ handlerMethod: methodName,
330
+ swagger: classSwagger,
331
+ })
332
+ }
333
+
334
+ return slots
335
+ }
336
+
222
337
  function emptyRequest() {
223
338
  return {
224
339
  method: '',
@@ -372,17 +487,21 @@ function router(routePath, options = {}) {
372
487
  )
373
488
  }
374
489
 
490
+ const classSwagger = {
491
+ tags: Array.isArray(options.tags) ? options.tags : [],
492
+ description: options.desc ?? null,
493
+ title: options.title ?? null,
494
+ deprecated: !!options.deprecated,
495
+ }
496
+
375
497
  registry.push({
376
498
  path: resolved,
499
+ classBasePath: resolved,
377
500
  ApiClass,
378
501
  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
- },
502
+ swagger: classSwagger,
385
503
  version_prefix: v,
504
+ slots: collectRouteSlots(ApiClass, resolved, classSwagger),
386
505
  })
387
506
  return ApiClass
388
507
  }
@@ -612,18 +731,18 @@ function fillOpenApiPaths(openapi, versionFilter = null) {
612
731
 
613
732
  for (const item of registry) {
614
733
  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] = {}
734
+ const { ApiClass, swagger: routeSwagger } = item
735
+ const slots = item.slots || []
620
736
 
621
- for (const methodName of HTTP_METHODS) {
622
- if (!definesMethod(ApiClass, methodName)) continue
737
+ for (const slot of slots) {
738
+ const pathParams = parsePathParams(slot.path)
739
+ const resolvedPath = slot.path.startsWith('/') ? slot.path : `/${slot.path}`
740
+ const routeSwaggerEntry = slot.swagger || routeSwagger
623
741
 
624
- const methodUpper = String(methodName).toUpperCase()
625
- const methodLower = String(methodName).toLowerCase()
742
+ if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
626
743
 
744
+ const methodLower = String(slot.httpMethod).toLowerCase()
745
+ const methodUpper = methodLower.toUpperCase()
627
746
  const params = pathParams.map((name) => ({
628
747
  name,
629
748
  in: 'path',
@@ -632,11 +751,11 @@ function fillOpenApiPaths(openapi, versionFilter = null) {
632
751
  }))
633
752
 
634
753
  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}`,
754
+ tags: routeSwaggerEntry?.tags?.length ? routeSwaggerEntry.tags : [],
755
+ summary: routeSwaggerEntry?.title ?? `${ApiClass.name}.${slot.handlerMethod}`,
756
+ description: routeSwaggerEntry?.description ?? '',
757
+ deprecated: !!routeSwaggerEntry?.deprecated,
758
+ operationId: `${ApiClass.name}_${slot.handlerMethod}`,
640
759
  parameters: params,
641
760
  responses: { 200: { description: 'OK' } },
642
761
  }
@@ -751,16 +870,16 @@ class FusionApp {
751
870
  if (this.mounted) return
752
871
  activeGlobalMiddleware = [...this._middleware]
753
872
 
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) => {
873
+ for (const { ApiClass, middleware: routeMiddleware = [], slots = [] } of registry) {
874
+ for (const slot of slots) {
875
+ const handlerMethod = slot.handlerMethod
876
+ this.engine.route(String(slot.httpMethod).toUpperCase(), slot.path, (errOrRequest, maybeRequest) => {
758
877
  const request = nativeRequestArg(errOrRequest, maybeRequest)
759
878
  const chain = [...activeGlobalMiddleware, ...routeMiddleware]
760
879
  const handler = async (req) => {
761
880
  try {
762
881
  const instance = new ApiClass(req || emptyRequest())
763
- const fn = instance[methodName]
882
+ const fn = instance[handlerMethod]
764
883
  return await Promise.resolve(fn.call(instance))
765
884
  } catch (err) {
766
885
  if (err instanceof HTTPException) return err.toResponse()
@@ -852,8 +971,17 @@ module.exports = {
852
971
  HTTPException,
853
972
  router,
854
973
  route,
974
+ httpGet,
975
+ httpPost,
976
+ httpPut,
977
+ httpPatch,
978
+ httpDelete,
979
+ httpHead,
980
+ httpOptions,
855
981
  apiResourceName,
856
982
  resolveRoutePath,
983
+ resolveHandlerRoute,
984
+ apiActionName,
857
985
  configure,
858
986
  getSettings,
859
987
  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.4",
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",