azurajs-scalar 1.0.0

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,43 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eduardo Developer <https://github.com/D3vEduardo>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ ## Contribution Guidelines
26
+
27
+ Contributions to this library are welcome! You can contribute by forking this repository and submitting a pull request.
28
+
29
+ **Important Notes:**
30
+ - All contributions are subject to review and acceptance by the project maintainer
31
+ - Submitting a pull request does not grant contributor copyright or ownership rights to the library
32
+ - The original copyright and ownership remains with Eduardo Developer
33
+ - Contributors are acknowledged in good faith, but do not receive legal ownership or copyright claims
34
+ - By contributing, you agree that your contributions become part of the library under the same MIT License
35
+
36
+ **How to Contribute:**
37
+ 1. Fork the repository
38
+ 2. Create a feature branch
39
+ 3. Make your changes
40
+ 4. Submit a pull request
41
+ 5. Please read this license carefully before contributing
42
+
43
+ For more information about this license, please refer to the full license text above.
package/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @azurajs/scalar 📘
2
+
3
+ > Proxy middleware and controller to scalar documentation. 🔗
4
+
5
+ ---
6
+
7
+ ## 🚀 Features
8
+
9
+ - ⚡ Fast and lightweight proxy middleware
10
+ - 🎨 Customizable HTML templates
11
+ - 🌐 Seamless integration with Scalar API references
12
+ - 🔧 Easy configuration and setup
13
+
14
+ ---
15
+
16
+ ## 📦 Installation
17
+
18
+ Choose your favorite package manager:
19
+
20
+ ### npm
21
+ ```bash
22
+ npm i @azurajs/scalar
23
+ ```
24
+
25
+ ### pnpm
26
+ ```bash
27
+ pnpm i @azurajs/scalar
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 🛠️ Usage
33
+
34
+ ### Configuration
35
+
36
+ #### src/index.ts
37
+
38
+ ```ts
39
+ import { AzuraClient } from "azurajs";
40
+ import { Scalar } from "@azurajs/scalar";
41
+ import path from "path";
42
+ import { fileURLToPath } from "url";
43
+ import { applyDecorators } from "azurajs/decorators";
44
+ import * from "./controllers"
45
+
46
+
47
+ const app = new AzuraClient();
48
+
49
+ const __filename = fileURLToPath(import.meta.url); // Optional
50
+ const __dirname = path.dirname(__filename); // Optional
51
+
52
+ const scalar = new Scalar({
53
+ apiSpecUrl: "http://localhost:4002/api-spec.json",
54
+ proxyUrl: "http://localhost:4002",
55
+ customHtmlPath: path.join(__dirname, "./public/html/api-docs.html"), // Optional
56
+ });
57
+
58
+ applyDecorators(app, Object.values(Controllers))
59
+ ```
60
+
61
+ ### Controller Setup
62
+
63
+ #### src/controllers/docs.controller.ts
64
+
65
+ ```ts
66
+ import { Controller, Get, Res } from "azurajs/decorators";
67
+ import { ResponseServer } from "azurajs/types";
68
+ import { getScalarDocs } from "@azurajs/scalar";
69
+
70
+ @Controller("/docs")
71
+ export class DocsController {
72
+ @Get("/")
73
+ scalarDocs(@Res() res: ResponseServer) {
74
+ getScalarDocs(res);
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Export Controllers
80
+
81
+ #### src/controllers/index.ts
82
+
83
+ ```ts
84
+ export * from "./docs.controller.ts";
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 🎨 Custom HTML Template
90
+
91
+ Create a custom HTML template for your API documentation with full control over styling and layout.
92
+
93
+ ### Example:
94
+
95
+ ```html
96
+ <!doctype html>
97
+ <html>
98
+ <head>
99
+ <title>Versum API - Sistine Chapel Theme</title>
100
+ <meta charset="utf-8" />
101
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
102
+
103
+ <link
104
+ href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap"
105
+ rel="stylesheet"
106
+ />
107
+
108
+ <style>
109
+ :root {
110
+ --scalar-font: "Libre Baskerville", serif;
111
+ --scalar-font-code: "Courier New", monospace;
112
+ }
113
+
114
+ .light-mode {
115
+ --scalar-color-1: #2b2b26;
116
+ --scalar-color-2: #5f5a4f;
117
+ --scalar-color-accent: #8b3f2f;
118
+ --scalar-background-1: #f8f3ea;
119
+ --scalar-background-2: #efe6d6;
120
+ --scalar-background-3: #e3d7c3;
121
+ --scalar-border-color: rgba(139, 63, 47, 0.18);
122
+ }
123
+
124
+ .dark-mode {
125
+ --scalar-color-1: #f3eadb;
126
+ --scalar-color-2: #d6c7a1;
127
+ --scalar-color-accent: #b86b2f;
128
+ --scalar-background-1: #1c170f;
129
+ --scalar-background-2: #241e14;
130
+ --scalar-background-3: #2e2619;
131
+ --scalar-border-color: rgba(184, 107, 47, 0.25);
132
+ }
133
+
134
+ h1,
135
+ h2,
136
+ h3 {
137
+ font-family: "Cinzel", serif;
138
+ font-weight: 700;
139
+ color: var(--scalar-color-accent) !important;
140
+ letter-spacing: 0.5px;
141
+ }
142
+ </style>
143
+ </head>
144
+
145
+ <body>
146
+ <div id="app">Carregando...</div>
147
+
148
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
149
+ <script>
150
+ // Busca o spec da porta 4002
151
+ fetch("&{api_spec_url}")
152
+ .then((res) => {
153
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
154
+ return res.json();
155
+ })
156
+ .then((spec) => {
157
+ console.log("Spec carregado:", spec);
158
+
159
+ Scalar.createApiReference("#app", {
160
+ spec: {
161
+ content: spec,
162
+ },
163
+ proxy: "&{proxy_url}",
164
+ theme: "none",
165
+ showSidebar: true,
166
+ withDefaultFonts: false,
167
+ });
168
+ })
169
+ .catch((err) => {
170
+ console.error("Erro ao carregar spec:", err);
171
+ document.getElementById("app").innerHTML =
172
+ `<pre style="color: red; padding: 20px;">
173
+ Erro ao carregar API spec: ${err.message}
174
+
175
+ Verifique se a API está rodando em &{proxy_url}
176
+ </pre>`;
177
+ });
178
+ </script>
179
+ </body>
180
+ </html>
181
+ ```
182
+
183
+ ### 🔧 External Variables
184
+
185
+ - `&{proxy_url}` - The proxy URL configured in your [configuration](####src/index.ts)
186
+ - `&{api_spec_url}` - The API specification URL configured in your [configuration](####src/index.ts)
187
+
188
+ ---
189
+
190
+ ## 🌐 References
191
+
192
+ - [AzuraJS](https://github.com/azurajs/azura) - The framework this package integrates with
193
+ - [Scalar](https://scalar.com/products/api-references/integrations/html-js) - The API reference tool
194
+
195
+ ---
196
+
197
+ ## 👨‍💻 Author
198
+
199
+ **Eduardo Developer**
200
+ [![GitHub Profile](https://img.shields.io/badge/GitHub-D3vEduardo-blue?logo=github)](https://github.com/D3vEduardo)
201
+
202
+ ---
203
+
204
+ ## 📄 License
205
+
206
+ This project is licensed under the [MIT License](./LICENSE) - see the LICENSE file for details.
207
+
208
+ ---
209
+
210
+ <p align="center">
211
+ Made with ❤️ by <a href="https://github.com/D3vEduardo">@D3vEduardo</a>
212
+ </p>
@@ -0,0 +1,41 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Versum API - Sistine Chapel Theme</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ </head>
8
+
9
+ <body>
10
+ <div id="app">Carregando...</div>
11
+
12
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
13
+ <script>
14
+ fetch("&{api_spec_url}")
15
+ .then((res) => {
16
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
17
+ return res.json();
18
+ })
19
+ .then((spec) => {
20
+ console.log("Spec carregado:", spec);
21
+
22
+ Scalar.createApiReference("#app", {
23
+ spec: {
24
+ content: spec,
25
+ },
26
+ proxy: "&{proxy_url}",
27
+ theme: "default",
28
+ });
29
+ })
30
+ .catch((err) => {
31
+ console.error("Erro ao carregar spec:", err);
32
+ document.getElementById("app").innerHTML =
33
+ `<pre style="color: red; padding: 20px;">
34
+ Erro ao carregar API spec: ${err.message}
35
+
36
+ Verifique se a API está rodando em &{proxy_url}
37
+ </pre>`;
38
+ });
39
+ </script>
40
+ </body>
41
+ </html>
@@ -0,0 +1,116 @@
1
+ import { RequestServer, ResponseServer, NextFunction } from 'azurajs/types';
2
+
3
+ interface ScalarConfigType {
4
+ apiSpecUrl: string;
5
+ proxyUrl: string;
6
+ customHtmlPath?: string;
7
+ }
8
+ interface ScalarStoreType {
9
+ proxy_url?: string;
10
+ api_spec_url?: string;
11
+ custom_html_path?: string;
12
+ }
13
+ type ProxyMiddlewareType = (req: RequestServer, res: ResponseServer, next?: NextFunction) => Promise<void>;
14
+ interface ProxyOptions {
15
+ apiSpecUrl: string;
16
+ }
17
+
18
+ /**
19
+ * @fileoverview Scalar client for API documentation proxy
20
+ * This module provides a client for setting up API documentation with proxy capabilities
21
+ */
22
+
23
+ /**
24
+ * Scalar client class for managing API documentation proxy
25
+ */
26
+ declare class Scalar {
27
+ /**
28
+ * Middleware function that handles proxying requests to the API specification
29
+ */
30
+ proxyMiddleware: ProxyMiddlewareType;
31
+ /**
32
+ * Creates a new Scalar client instance
33
+ * @param config Configuration object for the Scalar client
34
+ */
35
+ constructor(config: ScalarConfigType);
36
+ /**
37
+ * Validates the provided configuration
38
+ * @param config Configuration object to validate
39
+ * @throws ScalarError if configuration is invalid
40
+ */
41
+ private validateConfig;
42
+ }
43
+
44
+ /**
45
+ * @fileoverview Controller for serving Scalar API documentation
46
+ * This module handles serving the API documentation HTML with proper URL replacements
47
+ */
48
+
49
+ /**
50
+ * Serves the Scalar API documentation HTML page
51
+ * Replaces placeholders in the HTML template with actual URLs from the store
52
+ * @param res Response object to send the HTML to
53
+ * @returns Promise that resolves when the response is sent
54
+ */
55
+ declare function getScalarDocs(res: ResponseServer): Promise<ResponseServer>;
56
+
57
+ /**
58
+ * @fileoverview Proxy middleware for API requests
59
+ * This module provides a proxy middleware that forwards requests to the API specification
60
+ */
61
+
62
+ /**
63
+ * Creates a proxy middleware function that forwards requests to the API specification
64
+ * @param options Configuration options for the proxy
65
+ * @returns Middleware function that handles proxying requests
66
+ */
67
+ declare function proxyMiddleware({ apiSpecUrl, }: ProxyOptions): (req: RequestServer, res: ResponseServer, next?: NextFunction) => Promise<void>;
68
+
69
+ /**
70
+ * @fileoverview Store utility for managing application state
71
+ * This module provides a typed store for managing scalar configuration values
72
+ */
73
+
74
+ /**
75
+ * Typed store class for managing scalar configuration values
76
+ */
77
+ declare class ScalarStore {
78
+ private store;
79
+ /**
80
+ * Sets a value in the store
81
+ * @param key The key to set
82
+ * @param value The value to set (if undefined, the key will be removed)
83
+ */
84
+ set<K extends keyof ScalarStoreType>(key: K, value: ScalarStoreType[K]): void;
85
+ /**
86
+ * Gets a value from the store
87
+ * @param key The key to get
88
+ * @returns The value associated with the key
89
+ */
90
+ get<K extends keyof ScalarStoreType>(key: K): ScalarStoreType[K];
91
+ /**
92
+ * Checks if a key exists in the store
93
+ * @param key The key to check
94
+ * @returns True if the key exists, false otherwise
95
+ */
96
+ has(key: keyof ScalarStoreType): boolean;
97
+ /**
98
+ * Deletes a key from the store
99
+ * @param key The key to delete
100
+ * @returns True if the key existed and was deleted, false otherwise
101
+ */
102
+ delete(key: keyof ScalarStoreType): boolean;
103
+ /**
104
+ * Clears all values from the store
105
+ */
106
+ clear(): void;
107
+ /**
108
+ * Gets all values from the store
109
+ * @returns An object containing all key-value pairs in the store
110
+ */
111
+ getAll(): ScalarStoreType;
112
+ [key: string]: any;
113
+ }
114
+ declare const store: ScalarStore;
115
+
116
+ export { type ProxyMiddlewareType, Scalar, type ScalarConfigType, getScalarDocs, proxyMiddleware, store };
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ // src/config/types.ts
2
+ var ScalarError = class _ScalarError extends Error {
3
+ constructor(message, code, statusCode) {
4
+ super(message);
5
+ this.code = code;
6
+ this.statusCode = statusCode;
7
+ Object.setPrototypeOf(this, _ScalarError.prototype);
8
+ }
9
+ };
10
+
11
+ // src/middleware/proxy.ts
12
+ import { logger } from "azurajs/logger";
13
+ function proxyMiddleware({
14
+ apiSpecUrl
15
+ }) {
16
+ return async (req, res, _next) => {
17
+ try {
18
+ if (!req.url || !req.method) {
19
+ res.status(400).json({
20
+ error: "Request URL and method are required",
21
+ code: "INVALID_REQUEST"
22
+ });
23
+ return;
24
+ }
25
+ logger("info", `[PROXY] ${req.method} ${req.url}`);
26
+ const url = new URL(req.url, "http://localhost");
27
+ const targetUrl = apiSpecUrl + url.pathname + url.search;
28
+ logger("info", `[PROXY] Forwarding to: ${targetUrl}`);
29
+ const headers = {};
30
+ for (const [key, value] of Object.entries(req.headers)) {
31
+ if (value) {
32
+ headers[key] = Array.isArray(value) ? value.join(", ") : value;
33
+ }
34
+ }
35
+ headers.host = new URL(apiSpecUrl).host;
36
+ let body = null;
37
+ if (!["GET", "HEAD"].includes(req.method)) {
38
+ if (req.body) {
39
+ if (typeof req.body === "string") {
40
+ body = req.body;
41
+ } else if (typeof req.body === "object") {
42
+ body = JSON.stringify(req.body);
43
+ }
44
+ }
45
+ }
46
+ const proxyRes = await fetch(targetUrl, {
47
+ method: req.method,
48
+ headers,
49
+ body
50
+ });
51
+ logger("info", `[PROXY] Response: ${proxyRes.status}`);
52
+ const responseHeaders = {
53
+ "Access-Control-Allow-Origin": "*",
54
+ "Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
55
+ "Access-Control-Allow-Headers": "*"
56
+ };
57
+ for (const [key, value] of proxyRes.headers.entries()) {
58
+ if (!key.toLowerCase().startsWith("access-control-")) {
59
+ responseHeaders[key] = value;
60
+ }
61
+ }
62
+ res.writeHead(proxyRes.status, responseHeaders);
63
+ if (req.method === "OPTIONS") {
64
+ res.end();
65
+ return;
66
+ }
67
+ const buffer = Buffer.from(await proxyRes.arrayBuffer());
68
+ res.end(buffer);
69
+ } catch (err) {
70
+ logger("error", `[PROXY] Error: ${err}`);
71
+ res.writeHead(500, { "Content-Type": "application/json" });
72
+ res.end(JSON.stringify({
73
+ error: "Proxy error",
74
+ details: err instanceof Error ? err.message : String(err),
75
+ code: "PROXY_ERROR"
76
+ }));
77
+ }
78
+ };
79
+ }
80
+
81
+ // src/utils/store.ts
82
+ var ScalarStore = class {
83
+ constructor() {
84
+ this.store = /* @__PURE__ */ new Map();
85
+ }
86
+ /**
87
+ * Sets a value in the store
88
+ * @param key The key to set
89
+ * @param value The value to set (if undefined, the key will be removed)
90
+ */
91
+ set(key, value) {
92
+ this.store.set(key, value);
93
+ }
94
+ /**
95
+ * Gets a value from the store
96
+ * @param key The key to get
97
+ * @returns The value associated with the key
98
+ */
99
+ get(key) {
100
+ return this.store.get(key);
101
+ }
102
+ /**
103
+ * Checks if a key exists in the store
104
+ * @param key The key to check
105
+ * @returns True if the key exists, false otherwise
106
+ */
107
+ has(key) {
108
+ return this.store.has(key);
109
+ }
110
+ /**
111
+ * Deletes a key from the store
112
+ * @param key The key to delete
113
+ * @returns True if the key existed and was deleted, false otherwise
114
+ */
115
+ delete(key) {
116
+ return this.store.delete(key);
117
+ }
118
+ /**
119
+ * Clears all values from the store
120
+ */
121
+ clear() {
122
+ this.store.clear();
123
+ }
124
+ /**
125
+ * Gets all values from the store
126
+ * @returns An object containing all key-value pairs in the store
127
+ */
128
+ getAll() {
129
+ const result = {};
130
+ for (const [key, value] of this.store.entries()) {
131
+ result[key] = value;
132
+ }
133
+ return result;
134
+ }
135
+ };
136
+ var store = new ScalarStore();
137
+
138
+ // src/client.ts
139
+ var Scalar = class {
140
+ /**
141
+ * Creates a new Scalar client instance
142
+ * @param config Configuration object for the Scalar client
143
+ */
144
+ constructor(config) {
145
+ this.validateConfig(config);
146
+ store.set("proxy_url", config.proxyUrl);
147
+ store.set("api_spec_url", config.apiSpecUrl);
148
+ store.set("custom_html_path", config.customHtmlPath);
149
+ this.proxyMiddleware = proxyMiddleware({ apiSpecUrl: config.apiSpecUrl });
150
+ }
151
+ /**
152
+ * Validates the provided configuration
153
+ * @param config Configuration object to validate
154
+ * @throws ScalarError if configuration is invalid
155
+ */
156
+ validateConfig(config) {
157
+ if (!config.apiSpecUrl) {
158
+ throw new ScalarError(
159
+ "apiSpecUrl is required",
160
+ "MISSING_API_SPEC_URL",
161
+ 400
162
+ );
163
+ }
164
+ if (!config.proxyUrl) {
165
+ throw new ScalarError(
166
+ "proxyUrl is required",
167
+ "MISSING_PROXY_URL",
168
+ 400
169
+ );
170
+ }
171
+ try {
172
+ new URL(config.apiSpecUrl);
173
+ } catch (error) {
174
+ throw new ScalarError(
175
+ "apiSpecUrl must be a valid URL",
176
+ "INVALID_API_SPEC_URL",
177
+ 400
178
+ );
179
+ }
180
+ try {
181
+ new URL(config.proxyUrl);
182
+ } catch (error) {
183
+ throw new ScalarError(
184
+ "proxyUrl must be a valid URL",
185
+ "INVALID_PROXY_URL",
186
+ 400
187
+ );
188
+ }
189
+ }
190
+ };
191
+
192
+ // src/controller.ts
193
+ import { fileURLToPath } from "url";
194
+ import path from "path";
195
+ import fs from "fs";
196
+ import { logger as logger2 } from "azurajs/logger";
197
+ var __filename = fileURLToPath(import.meta.url);
198
+ var __dirname = path.dirname(__filename);
199
+ async function getScalarDocs(res) {
200
+ try {
201
+ const customHtmlPath = store.get("custom_html_path");
202
+ const proxyUrl = store.get("proxy_url");
203
+ const apiSpecUrl = store.get("api_spec_url");
204
+ if (!proxyUrl || !apiSpecUrl) {
205
+ throw new ScalarError(
206
+ "Proxy URL or API Spec URL not defined in store",
207
+ "STORE_VALUES_MISSING",
208
+ 500
209
+ );
210
+ }
211
+ const htmlPath = customHtmlPath || path.join(__dirname, "./api-docs.html");
212
+ if (!fs.existsSync(htmlPath)) {
213
+ throw new ScalarError(
214
+ `HTML template file not found at path: ${htmlPath}`,
215
+ "HTML_TEMPLATE_NOT_FOUND",
216
+ 404
217
+ );
218
+ }
219
+ const html = fs.readFileSync(htmlPath, "utf-8");
220
+ const processedHtml = html.replace(/&{proxy_url}/g, proxyUrl).replace(/&{api_spec_url}/g, apiSpecUrl);
221
+ return res.send(processedHtml);
222
+ } catch (error) {
223
+ if (error instanceof ScalarError) {
224
+ logger2("error", `[ScalarError] ${error.message}`);
225
+ return res.status(error.statusCode).json({
226
+ message: error.message,
227
+ code: error.code
228
+ });
229
+ } else {
230
+ logger2("error", String(error));
231
+ return res.status(500).json({
232
+ message: "Internal server error occurred while serving documentation",
233
+ code: "INTERNAL_SERVER_ERROR"
234
+ });
235
+ }
236
+ }
237
+ }
238
+ export {
239
+ Scalar,
240
+ ScalarError,
241
+ getScalarDocs,
242
+ proxyMiddleware,
243
+ store
244
+ };
245
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/types.ts","../src/middleware/proxy.ts","../src/utils/store.ts","../src/client.ts","../src/controller.ts"],"sourcesContent":["import { NextFunction, RequestServer, ResponseServer } from \"azurajs/types\";\n\nexport interface ScalarConfigType {\n apiSpecUrl: string;\n proxyUrl: string;\n customHtmlPath?: string; // Optional since it might not always be provided\n}\n\nexport interface ScalarStoreType {\n proxy_url?: string;\n api_spec_url?: string;\n custom_html_path?: string;\n}\n\nexport type ProxyMiddlewareType = (\n req: RequestServer,\n res: ResponseServer,\n next?: NextFunction,\n) => Promise<void>;\n\n// Define specific error types\nexport class ScalarError extends Error {\n public readonly code: string;\n public readonly statusCode: number;\n\n constructor(message: string, code: string, statusCode: number) {\n super(message);\n this.code = code;\n this.statusCode = statusCode;\n Object.setPrototypeOf(this, ScalarError.prototype);\n }\n}\n\nexport interface ProxyOptions {\n apiSpecUrl: string;\n}\n","/**\n * @fileoverview Proxy middleware for API requests\n * This module provides a proxy middleware that forwards requests to the API specification\n */\n\nimport { ProxyOptions } from \"../config/types\";\nimport { NextFunction, RequestServer, ResponseServer } from \"azurajs/types\";\nimport { logger } from \"azurajs/logger\";\n\n/**\n * Creates a proxy middleware function that forwards requests to the API specification\n * @param options Configuration options for the proxy\n * @returns Middleware function that handles proxying requests\n */\nexport function proxyMiddleware({\n apiSpecUrl,\n}: ProxyOptions): (\n req: RequestServer,\n res: ResponseServer,\n next?: NextFunction,\n) => Promise<void> {\n return async (\n req: RequestServer,\n res: ResponseServer,\n _next?: NextFunction,\n ): Promise<void> => {\n try {\n if (!req.url || !req.method) {\n res.status(400).json({\n error: \"Request URL and method are required\",\n code: \"INVALID_REQUEST\"\n });\n return;\n }\n\n logger(\"info\", `[PROXY] ${req.method} ${req.url}`);\n\n const url = new URL(req.url, \"http://localhost\");\n const targetUrl = apiSpecUrl + url.pathname + url.search;\n\n logger(\"info\", `[PROXY] Forwarding to: ${targetUrl}`);\n\n // Convert Node.js headers to a format compatible with fetch\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n headers.host = new URL(apiSpecUrl).host;\n\n // Prepare request body\n let body: BodyInit | null = null;\n if (![\"GET\", \"HEAD\"].includes(req.method)) {\n // For non-GET/HEAD requests, we need to properly serialize the body\n // Since req might contain various data types, we need to handle appropriately\n if (req.body) {\n if (typeof req.body === 'string') {\n body = req.body;\n } else if (typeof req.body === 'object') {\n body = JSON.stringify(req.body);\n }\n }\n }\n\n const proxyRes = await fetch(targetUrl, {\n method: req.method,\n headers,\n body,\n });\n\n logger(\"info\", `[PROXY] Response: ${proxyRes.status}`);\n\n // CORS - IMPORTANT: define before copying response headers\n const responseHeaders: Record<string, string> = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET,POST,PUT,PATCH,DELETE,OPTIONS\",\n \"Access-Control-Allow-Headers\": \"*\",\n };\n\n // Copy response headers, avoiding CORS header conflicts\n for (const [key, value] of proxyRes.headers.entries()) {\n // Don't overwrite CORS headers\n if (!key.toLowerCase().startsWith(\"access-control-\")) {\n responseHeaders[key] = value;\n }\n }\n\n res.writeHead(proxyRes.status, responseHeaders);\n\n if (req.method === \"OPTIONS\") {\n res.end();\n return;\n }\n\n const buffer = Buffer.from(await proxyRes.arrayBuffer());\n res.end(buffer);\n } catch (err) {\n logger(\"error\", `[PROXY] Error: ${err}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n error: \"Proxy error\",\n details: err instanceof Error ? err.message : String(err),\n code: \"PROXY_ERROR\"\n }));\n }\n };\n}\n","/**\n * @fileoverview Store utility for managing application state\n * This module provides a typed store for managing scalar configuration values\n */\n\nimport { ScalarStoreType } from \"../config/types\";\n\n/**\n * Typed store class for managing scalar configuration values\n */\nclass ScalarStore {\n private store: Map<keyof ScalarStoreType, string | undefined> = new Map();\n\n /**\n * Sets a value in the store\n * @param key The key to set\n * @param value The value to set (if undefined, the key will be removed)\n */\n set<K extends keyof ScalarStoreType>(key: K, value: ScalarStoreType[K]): void {\n this.store.set(key, value as string | undefined);\n }\n\n /**\n * Gets a value from the store\n * @param key The key to get\n * @returns The value associated with the key\n */\n get<K extends keyof ScalarStoreType>(key: K): ScalarStoreType[K] {\n return this.store.get(key) as ScalarStoreType[K];\n }\n\n /**\n * Checks if a key exists in the store\n * @param key The key to check\n * @returns True if the key exists, false otherwise\n */\n has(key: keyof ScalarStoreType): boolean {\n return this.store.has(key);\n }\n\n /**\n * Deletes a key from the store\n * @param key The key to delete\n * @returns True if the key existed and was deleted, false otherwise\n */\n delete(key: keyof ScalarStoreType): boolean {\n return this.store.delete(key);\n }\n\n /**\n * Clears all values from the store\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Gets all values from the store\n * @returns An object containing all key-value pairs in the store\n */\n getAll(): ScalarStoreType {\n const result: Partial<ScalarStoreType> = {};\n for (const [key, value] of this.store.entries()) {\n result[key] = value as ScalarStoreType[keyof ScalarStoreType];\n }\n return result as ScalarStoreType;\n }\n\n // Index signature to allow direct property access\n [key: string]: any;\n}\n\nexport const store = new ScalarStore();\n","/**\n * @fileoverview Scalar client for API documentation proxy\n * This module provides a client for setting up API documentation with proxy capabilities\n */\n\nimport { ProxyMiddlewareType, ScalarConfigType, ScalarError } from \"./config/types\";\nimport { proxyMiddleware } from \"./middleware/proxy\";\nimport { store } from \"./utils/store\";\n\n/**\n * Scalar client class for managing API documentation proxy\n */\nexport class Scalar {\n /**\n * Middleware function that handles proxying requests to the API specification\n */\n public proxyMiddleware: ProxyMiddlewareType;\n\n /**\n * Creates a new Scalar client instance\n * @param config Configuration object for the Scalar client\n */\n constructor(config: ScalarConfigType) {\n this.validateConfig(config);\n\n store.set(\"proxy_url\", config.proxyUrl);\n store.set(\"api_spec_url\", config.apiSpecUrl);\n store.set(\"custom_html_path\", config.customHtmlPath);\n\n this.proxyMiddleware = proxyMiddleware({ apiSpecUrl: config.apiSpecUrl });\n }\n\n /**\n * Validates the provided configuration\n * @param config Configuration object to validate\n * @throws ScalarError if configuration is invalid\n */\n private validateConfig(config: ScalarConfigType): void {\n if (!config.apiSpecUrl) {\n throw new ScalarError(\n \"apiSpecUrl is required\",\n \"MISSING_API_SPEC_URL\",\n 400\n );\n }\n\n if (!config.proxyUrl) {\n throw new ScalarError(\n \"proxyUrl is required\",\n \"MISSING_PROXY_URL\",\n 400\n );\n }\n\n try {\n new URL(config.apiSpecUrl);\n } catch (error) {\n throw new ScalarError(\n \"apiSpecUrl must be a valid URL\",\n \"INVALID_API_SPEC_URL\",\n 400\n );\n }\n\n try {\n new URL(config.proxyUrl);\n } catch (error) {\n throw new ScalarError(\n \"proxyUrl must be a valid URL\",\n \"INVALID_PROXY_URL\",\n 400\n );\n }\n }\n}\n","/**\n * @fileoverview Controller for serving Scalar API documentation\n * This module handles serving the API documentation HTML with proper URL replacements\n */\n\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\nimport fs from \"fs\";\nimport { logger } from \"azurajs/logger\";\nimport { ResponseServer } from \"azurajs/types\";\nimport { store } from \"./utils/store\";\nimport { ScalarError } from \"./config/types\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Serves the Scalar API documentation HTML page\n * Replaces placeholders in the HTML template with actual URLs from the store\n * @param res Response object to send the HTML to\n * @returns Promise that resolves when the response is sent\n */\nexport async function getScalarDocs(res: ResponseServer) {\n try {\n const customHtmlPath = store.get(\"custom_html_path\");\n const proxyUrl = store.get(\"proxy_url\");\n const apiSpecUrl = store.get(\"api_spec_url\");\n\n if (!proxyUrl || !apiSpecUrl) {\n throw new ScalarError(\n \"Proxy URL or API Spec URL not defined in store\",\n \"STORE_VALUES_MISSING\",\n 500\n );\n }\n\n const htmlPath = customHtmlPath || path.join(__dirname, \"./api-docs.html\");\n\n if (!fs.existsSync(htmlPath)) {\n throw new ScalarError(\n `HTML template file not found at path: ${htmlPath}`,\n \"HTML_TEMPLATE_NOT_FOUND\",\n 404\n );\n }\n\n const html = fs.readFileSync(htmlPath, \"utf-8\");\n const processedHtml = html\n .replace(/&{proxy_url}/g, proxyUrl)\n .replace(/&{api_spec_url}/g, apiSpecUrl);\n\n return res.send(processedHtml);\n } catch (error) {\n if (error instanceof ScalarError) {\n logger(\"error\", `[ScalarError] ${error.message}`);\n return res.status(error.statusCode).json({\n message: error.message,\n code: error.code,\n });\n } else {\n logger(\"error\", String(error));\n return res.status(500).json({\n message: \"Internal server error occurred while serving documentation\",\n code: \"INTERNAL_SERVER_ERROR\",\n });\n }\n }\n}\n"],"mappings":"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","names":["logger","logger"]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "azurajs-scalar",
3
+ "version": "1.0.0",
4
+ "description": "Proxy middleware and controller to scalar documentation.",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "types": "dist/index.d.ts",
10
+ "main": "dist/index.js",
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format esm --dts"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "packageManager": "pnpm@10.25.0",
18
+ "dependencies": {
19
+ "azurajs": "2.6.1-1"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^25.0.10",
23
+ "tsup": "^8.5.1",
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }