fusion-framework 1.1.2 → 1.2.1

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.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Fusion Framework
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -2,26 +2,24 @@
2
2
 
3
3
  Class-based HTTP APIs on a shared Rust core (`fusion-core`).
4
4
 
5
- Handlers use `this.params` / `this.query` / `this.body` / `this.state` (no signature param injection — that is Python-only DX).
5
+ Handlers use `this.params` / `this.query` / `this.body` / `this.state` (no signature param injection — that is Python-only).
6
6
 
7
- ## Links
7
+ ## Install
8
8
 
9
- - **Docs:** [fusion.cipherunit.xyz](https://fusion.cipherunit.xyz/)
10
- - **GitHub:** [cipherunits/fusion-framework](https://github.com/cipherunits/fusion-framework)
11
- - **CLI:** [cipherunits/fusion-tool](https://github.com/cipherunits/fusion-tool)
9
+ ```bash
10
+ npm i fusion-framework
11
+ ```
12
12
 
13
- ## Install
13
+ From this repo:
14
14
 
15
15
  ```bash
16
- cd crates/fusion-node
17
- npm install
18
- npm run build:debug
16
+ cd crates/fusion-node && npm install && npm run build:debug
19
17
  ```
20
18
 
21
- Or after publish:
19
+ Scaffold with [Fusion Tool](https://github.com/cipherunits/fusion-tool):
22
20
 
23
21
  ```bash
24
- npm i fusion-framework
22
+ fusion init --lang typescript --name my-app
25
23
  ```
26
24
 
27
25
  ## Quick start
@@ -37,7 +35,7 @@ export const ItemModule = route('/api/[module]/{id}')(
37
35
  },
38
36
  )
39
37
 
40
- const MIDDLEWARE = [] // optional framework has no defaults
38
+ const MIDDLEWARE = [] // your middleware; Fusion already adds frameworkHeaders() by default
41
39
 
42
40
  settings.ensureLoaded()
43
41
  const app = new FusionApp(getSettings())
@@ -45,32 +43,14 @@ for (const mw of MIDDLEWARE) app.use(mw)
45
43
  await app.listen()
46
44
  ```
47
45
 
48
- ## Middleware
49
-
50
- ```js
51
- import { bearerJwt, requireRoles, route } from 'fusion-framework'
52
-
53
- const MIDDLEWARE = [bearerJwt()] // or bearerJwt({ verify })
54
-
55
- route('/api/admin', { roles: ['admin', 'super_admin'] })(
56
- class AdminModule extends FusionBaseApi {
57
- get() {
58
- return this.response({ user: this.state.jwt?.sub })
59
- }
60
- },
61
- )
62
- ```
63
-
64
- Sync or async: `(request, callNext) => …` / `async (request, callNext) => await callNext(request)`.
46
+ ## Docs
65
47
 
66
- ## Status codes
48
+ Full guides (router, config, middleware):
49
+ **https://fusion.cipherunit.xyz/en/docs/typescript/v1**
67
50
 
68
- ```js
69
- import { status } from 'fusion-framework'
70
- status.HTTP_SUCCESS // 200
71
- status.HTTP_404_NOT_FOUND
72
- ```
51
+ - Site: [fusion.cipherunit.xyz](https://fusion.cipherunit.xyz/)
52
+ - GitHub: [cipherunits/fusion-framework](https://github.com/cipherunits/fusion-framework)
73
53
 
74
54
  ## License
75
55
 
76
- MIT
56
+ BSD 3-Clause
Binary file
Binary file
Binary file
Binary file
package/index.d.ts CHANGED
@@ -94,8 +94,27 @@ export function coerceParamJs(raw: string, kind?: string): unknown
94
94
 
95
95
  export const settings: Settings
96
96
  export const status: Record<string, number>
97
+ export const header: HeaderModule
97
98
  export const HTTP_METHODS: string[]
98
99
 
100
+ export interface HeaderModule {
101
+ [name: string]: string | ((...args: any[]) => Record<string, string>)
102
+ CONTENT_TYPE: string
103
+ CONTENT_DISPOSITION: string
104
+ LOCATION: string
105
+ AUTHORIZATION: string
106
+ APPLICATION_JSON: string
107
+ APPLICATION_OCTET_STREAM: string
108
+ APPLICATION_PDF: string
109
+ attachment(filename: string): Record<string, string>
110
+ inline(filename?: string | null): Record<string, string>
111
+ contentType(mediaType: string, charset?: string | null): Record<string, string>
112
+ location(url: string): Record<string, string>
113
+ cacheControl(value: string): Record<string, string>
114
+ download(filename: string, mediaType?: string | null): Record<string, string>
115
+ fingerprint(): Record<string, string>
116
+ }
117
+
99
118
  export interface FusionSettings {
100
119
  host: string
101
120
  port: number
@@ -118,6 +137,8 @@ export type FusionMiddleware = (
118
137
  callNext: (request: FusionRequest) => unknown | Promise<unknown>,
119
138
  ) => unknown | Promise<unknown>
120
139
 
140
+ export function frameworkHeaders(): FusionMiddleware
141
+
121
142
  export type FusionResponse =
122
143
  | string
123
144
  | {
package/index.js CHANGED
@@ -63,6 +63,111 @@ if (typeof native.getHttpStatusCodes === 'function') {
63
63
  })
64
64
  }
65
65
 
66
+ const header = Object.create(null)
67
+ if (typeof native.getHttpHeaderConstants === 'function') {
68
+ for (const entry of native.getHttpHeaderConstants()) {
69
+ header[entry.name] = entry.value
70
+ }
71
+ } else {
72
+ Object.assign(header, {
73
+ CONTENT_TYPE: 'Content-Type',
74
+ CONTENT_DISPOSITION: 'Content-Disposition',
75
+ LOCATION: 'Location',
76
+ AUTHORIZATION: 'Authorization',
77
+ APPLICATION_JSON: 'application/json',
78
+ APPLICATION_OCTET_STREAM: 'application/octet-stream',
79
+ APPLICATION_PDF: 'application/pdf',
80
+ })
81
+ }
82
+
83
+ function mergeHeaderMap(map) {
84
+ return map && typeof map === 'object' ? { ...map } : {}
85
+ }
86
+
87
+ header.attachment = (filename) =>
88
+ typeof native.headerAttachment === 'function'
89
+ ? mergeHeaderMap(native.headerAttachment(String(filename)))
90
+ : { [header.CONTENT_DISPOSITION]: `attachment; filename="${filename}"` }
91
+
92
+ header.inline = (filename) =>
93
+ typeof native.headerInline === 'function'
94
+ ? mergeHeaderMap(native.headerInline(filename == null ? null : String(filename)))
95
+ : filename
96
+ ? { [header.CONTENT_DISPOSITION]: `inline; filename="${filename}"` }
97
+ : { [header.CONTENT_DISPOSITION]: 'inline' }
98
+
99
+ header.contentType = (mediaType, charset) =>
100
+ typeof native.headerContentType === 'function'
101
+ ? mergeHeaderMap(native.headerContentType(String(mediaType), charset == null ? null : String(charset)))
102
+ : {
103
+ [header.CONTENT_TYPE]: charset
104
+ ? `${mediaType}; charset=${charset}`
105
+ : String(mediaType),
106
+ }
107
+
108
+ header.location = (url) =>
109
+ typeof native.headerLocation === 'function'
110
+ ? mergeHeaderMap(native.headerLocation(String(url)))
111
+ : { [header.LOCATION]: String(url) }
112
+
113
+ header.cacheControl = (value) =>
114
+ typeof native.headerCacheControl === 'function'
115
+ ? mergeHeaderMap(native.headerCacheControl(String(value)))
116
+ : { 'Cache-Control': String(value) }
117
+
118
+ header.download = (filename, mediaType) =>
119
+ typeof native.headerDownload === 'function'
120
+ ? mergeHeaderMap(
121
+ native.headerDownload(
122
+ String(filename),
123
+ mediaType == null ? null : String(mediaType),
124
+ ),
125
+ )
126
+ : {
127
+ ...header.contentType(mediaType || header.APPLICATION_OCTET_STREAM),
128
+ ...header.attachment(filename),
129
+ }
130
+
131
+ header.fingerprint = () =>
132
+ typeof native.getFingerprintHeaders === 'function'
133
+ ? mergeHeaderMap(native.getFingerprintHeaders())
134
+ : {
135
+ 'X-Powered-By': 'Fusion Framework',
136
+ 'X-Framework': 'Fusion',
137
+ 'X-Fusion-Version': '1.2.1',
138
+ }
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
+
162
+ function frameworkHeaders() {
163
+ const extra = header.fingerprint()
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)
168
+ }
169
+ }
170
+
66
171
  class FusionBaseApi {
67
172
  constructor(request) {
68
173
  this.request = request
@@ -127,18 +232,14 @@ function isResponse(value) {
127
232
 
128
233
  async function runMiddlewareChain(request, middlewares, handler) {
129
234
  ensureState(request)
130
- let index = 0
131
235
 
132
236
  async function dispatch(i, req) {
133
237
  if (i >= middlewares.length) {
134
- return await handler(req)
238
+ return await awaitMaybe(handler(req))
135
239
  }
136
240
  const middleware = middlewares[i]
137
241
  const callNext = (nextReq) => dispatch(i + 1, nextReq)
138
- let result = middleware(req, callNext)
139
- if (result && typeof result.then === 'function') {
140
- result = await result
141
- }
242
+ const result = await awaitMaybe(middleware(req, callNext))
142
243
  if (isResponse(result)) return result
143
244
  return result
144
245
  }
@@ -488,7 +589,8 @@ class FusionApp {
488
589
  this.settings = getSettings()
489
590
  this.engine = new NativeApp()
490
591
  this.mounted = false
491
- this._middleware = []
592
+ // Default: advertise Fusion to clients / Wappalyzer-style detectors.
593
+ this._middleware = [frameworkHeaders()]
492
594
  }
493
595
 
494
596
  use(middleware) {
@@ -639,10 +741,12 @@ module.exports = {
639
741
  getSettings,
640
742
  settings,
641
743
  status,
744
+ header,
642
745
  HTTP_METHODS,
643
746
  run,
644
747
  bearerJwt,
645
748
  requireRoles,
749
+ frameworkHeaders,
646
750
  runMiddlewareChain,
647
751
  coerceParam,
648
752
  getHttpMethods: () => HTTP_METHODS,
package/package.json CHANGED
@@ -1,25 +1,21 @@
1
1
  {
2
2
  "name": "fusion-framework",
3
- "version": "1.1.2",
4
- "description": "Build high-performance class-based HTTP APIs in Node.js on a shared Rust core routing, middleware, JWT auth, and TypeScript types",
3
+ "version": "1.2.1",
4
+ "description": "Class-based HTTP framework for Node.js, powered by a shared Rust core via N-API",
5
5
  "keywords": [
6
6
  "http",
7
7
  "framework",
8
8
  "nodejs",
9
- "node",
10
- "rust",
11
- "napi",
12
9
  "http-server",
13
10
  "web-framework",
14
- "rest-api",
11
+ "rest",
15
12
  "api",
16
13
  "routing",
17
14
  "middleware",
18
15
  "jwt",
19
16
  "typescript",
20
- "openapi",
21
- "swagger",
22
- "hyper",
17
+ "napi",
18
+ "rust",
23
19
  "fusion"
24
20
  ],
25
21
  "homepage": "https://fusion.cipherunit.xyz/",
@@ -32,21 +28,33 @@
32
28
  "directory": "crates/fusion-node"
33
29
  },
34
30
  "author": "CipherUnits <cipherunit.dev@gmail.com>",
35
- "license": "MIT",
36
- "main": "index.js",
37
- "types": "index.d.ts",
31
+ "license": "BSD-3-Clause",
32
+ "type": "commonjs",
33
+ "main": "./index.js",
34
+ "types": "./index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./index.d.ts",
38
+ "require": "./index.js",
39
+ "default": "./index.js"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
38
43
  "files": [
39
44
  "index.js",
40
45
  "index.d.ts",
41
46
  "README.md",
47
+ "LICENSE",
42
48
  "*.node"
43
49
  ],
44
50
  "napi": {
45
51
  "name": "fusion-node",
46
52
  "triples": {
47
- "defaults": true,
53
+ "defaults": false,
48
54
  "additional": [
55
+ "x86_64-unknown-linux-gnu",
49
56
  "aarch64-unknown-linux-gnu",
57
+ "x86_64-pc-windows-msvc",
50
58
  "aarch64-apple-darwin"
51
59
  ]
52
60
  }
@@ -55,7 +63,7 @@
55
63
  "artifacts": "napi artifacts",
56
64
  "build": "napi build --platform --release",
57
65
  "build:debug": "napi build --platform",
58
- "prepublishOnly": "napi prepublish -t npm --skip-gh-release"
66
+ "prepublishOnly": "node -e \"const fs=require('fs');const ok=fs.readdirSync('.').some(f=>f.endsWith('.node'));if(!ok){console.error('Missing native .node binaries before publish');process.exit(1)}\""
59
67
  },
60
68
  "publishConfig": {
61
69
  "access": "public",
@@ -63,16 +71,14 @@
63
71
  "provenance": true
64
72
  },
65
73
  "engines": {
66
- "node": ">=16"
74
+ "node": ">=18"
67
75
  },
76
+ "os": [
77
+ "linux",
78
+ "darwin",
79
+ "win32"
80
+ ],
68
81
  "devDependencies": {
69
82
  "@napi-rs/cli": "^2.18.4"
70
- },
71
- "optionalDependencies": {
72
- "fusion-framework-win32-x64-msvc": "1.1.2",
73
- "fusion-framework-darwin-x64": "1.1.2",
74
- "fusion-framework-linux-x64-gnu": "1.1.2",
75
- "fusion-framework-linux-arm64-gnu": "1.1.2",
76
- "fusion-framework-darwin-arm64": "1.1.2"
77
83
  }
78
- }
84
+ }