@engjts/nexus 0.1.6 → 0.1.8
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/BENCHMARK_REPORT.md +343 -0
- package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/cli/templates/generators.d.ts.map +1 -1
- package/dist/cli/templates/generators.js +16 -13
- package/dist/cli/templates/generators.js.map +1 -1
- package/dist/cli/templates/index.js +25 -25
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/advanced/playground/generatePlaygroundHTML.ts +107 -0
- package/src/advanced/playground/playground.ts +225 -145
- package/src/advanced/playground/types.ts +29 -0
- package/src/cli/templates/generators.ts +16 -13
- package/src/cli/templates/index.ts +25 -25
- package/src/core/application.ts +202 -84
- package/src/core/context-pool.ts +8 -56
- package/src/core/context.ts +497 -53
- package/src/core/index.ts +14 -0
- package/src/core/middleware.ts +99 -89
- package/src/core/router/file-router.ts +41 -12
- package/src/core/router/index.ts +213 -180
- package/src/core/router/radix-tree.ts +20 -4
- package/src/core/serializer.ts +397 -0
- package/src/core/types.ts +43 -1
- package/src/index.ts +17 -0
|
@@ -9,115 +9,195 @@ import { RouteConfig, HTTPMethod, Context, Plugin, SchemaConfig, RouteMeta } fro
|
|
|
9
9
|
import { PlaygroundConfig, StoredRoute } from './types';
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* API Playground Plugin
|
|
14
|
+
* Provides an interactive API explorer with authentication and development-mode security
|
|
15
|
+
*
|
|
16
|
+
* @param config - Configuration options for the playground
|
|
17
|
+
* @returns Plugin instance
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* app.use(playground({
|
|
22
|
+
* path: '/playground',
|
|
23
|
+
* developmentOnly: true,
|
|
24
|
+
* auth: {
|
|
25
|
+
* username: 'admin',
|
|
26
|
+
* password: 'secret123'
|
|
27
|
+
* }
|
|
28
|
+
* }));
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
12
31
|
export function playground(config: PlaygroundConfig = {}): Plugin {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
install(app: Application) {
|
|
34
|
-
appInstance = app;
|
|
35
|
-
const originalRoute = app.route.bind(app);
|
|
36
|
-
const originalGet = app.get.bind(app);
|
|
37
|
-
const originalPost = app.post.bind(app);
|
|
38
|
-
const originalPut = app.put.bind(app);
|
|
39
|
-
const originalDelete = app.delete.bind(app);
|
|
40
|
-
const originalPatch = app.patch.bind(app);
|
|
41
|
-
|
|
42
|
-
app.route = function (routeConfig: RouteConfig) {
|
|
43
|
-
routes.push({
|
|
44
|
-
method: routeConfig.method,
|
|
45
|
-
path: routeConfig.path,
|
|
46
|
-
schema: routeConfig.schema,
|
|
47
|
-
meta: routeConfig.meta
|
|
48
|
-
});
|
|
49
|
-
return originalRoute(routeConfig);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const wrapMethod = (method: HTTPMethod, original: Function) => {
|
|
53
|
-
return function (pathOrRoute: string | any, handlerOrConfig?: any) {
|
|
54
|
-
// Class-based routing
|
|
55
|
-
if (typeof pathOrRoute === 'object' && 'pathName' in pathOrRoute) {
|
|
56
|
-
const route = pathOrRoute;
|
|
57
|
-
routes.push({
|
|
58
|
-
method,
|
|
59
|
-
path: route.pathName,
|
|
60
|
-
schema: route.schema?.(),
|
|
61
|
-
meta: route.meta?.()
|
|
62
|
-
});
|
|
63
|
-
return original(pathOrRoute);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const path = pathOrRoute;
|
|
67
|
-
if (typeof handlerOrConfig !== 'function' && handlerOrConfig) {
|
|
68
|
-
routes.push({ method, path, schema: handlerOrConfig.schema, meta: handlerOrConfig.meta });
|
|
69
|
-
} else {
|
|
70
|
-
routes.push({ method, path });
|
|
71
|
-
}
|
|
72
|
-
return original(path, handlerOrConfig);
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
app.get = wrapMethod('GET', originalGet);
|
|
77
|
-
app.post = wrapMethod('POST', originalPost);
|
|
78
|
-
app.put = wrapMethod('PUT', originalPut);
|
|
79
|
-
app.delete = wrapMethod('DELETE', originalDelete);
|
|
80
|
-
app.patch = wrapMethod('PATCH', originalPatch);
|
|
81
|
-
|
|
82
|
-
const basePath = resolvedConfig.path!;
|
|
83
|
-
|
|
84
|
-
originalGet(basePath, async (ctx: any) => {
|
|
85
|
-
if (!detectedBaseUrl && ctx.raw?.req) {
|
|
86
|
-
const req = ctx.raw.req;
|
|
87
|
-
const protocol = req.headers['x-forwarded-proto'] || 'http';
|
|
88
|
-
const host = req.headers.host || 'localhost:3000';
|
|
89
|
-
detectedBaseUrl = `${protocol}://${host}`;
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
statusCode: 200,
|
|
93
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
94
|
-
body: generatePlaygroundHTML(resolvedConfig, detectedBaseUrl)
|
|
95
|
-
};
|
|
96
|
-
});
|
|
32
|
+
const resolvedConfig: PlaygroundConfig = {
|
|
33
|
+
path: '/playground',
|
|
34
|
+
title: 'API Playground',
|
|
35
|
+
theme: 'dark',
|
|
36
|
+
enableHistory: true,
|
|
37
|
+
maxHistory: 50,
|
|
38
|
+
enableVariables: true,
|
|
39
|
+
developmentOnly: true,
|
|
40
|
+
defaultHeaders: { 'Content-Type': 'application/json' },
|
|
41
|
+
variables: { baseUrl: 'http://localhost:3000', token: '' },
|
|
42
|
+
...config
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const routes: StoredRoute[] = [];
|
|
46
|
+
let detectedBaseUrl = '';
|
|
47
|
+
let appInstance: Application | null = null;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name: 'playground',
|
|
51
|
+
version: '1.0.0',
|
|
97
52
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
53
|
+
install(app: Application) {
|
|
54
|
+
// Check if playground should be disabled in production
|
|
55
|
+
if (resolvedConfig.developmentOnly && process.env.NODE_ENV === 'production') {
|
|
56
|
+
console.warn('⚠️ API Playground is disabled in production mode');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
appInstance = app;
|
|
61
|
+
const originalRoute = app.route.bind(app);
|
|
62
|
+
const originalGet = app.get.bind(app);
|
|
63
|
+
const originalPost = app.post.bind(app);
|
|
64
|
+
const originalPut = app.put.bind(app);
|
|
65
|
+
const originalDelete = app.delete.bind(app);
|
|
66
|
+
const originalPatch = app.patch.bind(app);
|
|
67
|
+
|
|
68
|
+
app.route = function (routeConfig: RouteConfig) {
|
|
69
|
+
routes.push({
|
|
70
|
+
method: routeConfig.method,
|
|
71
|
+
path: routeConfig.path,
|
|
72
|
+
schema: routeConfig.schema,
|
|
73
|
+
meta: routeConfig.meta
|
|
74
|
+
});
|
|
75
|
+
return originalRoute(routeConfig);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const wrapMethod = (method: HTTPMethod, original: Function) => {
|
|
79
|
+
return function (pathOrRoute: string | any, handlerOrConfig?: any) {
|
|
80
|
+
// Class-based routing
|
|
81
|
+
if (typeof pathOrRoute === 'object' && 'pathName' in pathOrRoute) {
|
|
82
|
+
const route = pathOrRoute;
|
|
83
|
+
routes.push({
|
|
84
|
+
method,
|
|
85
|
+
path: route.pathName,
|
|
86
|
+
schema: route.schema?.(),
|
|
87
|
+
meta: route.meta?.()
|
|
118
88
|
});
|
|
89
|
+
return original(pathOrRoute);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const path = pathOrRoute;
|
|
93
|
+
if (typeof handlerOrConfig !== 'function' && handlerOrConfig) {
|
|
94
|
+
routes.push({ method, path, schema: handlerOrConfig.schema, meta: handlerOrConfig.meta });
|
|
95
|
+
} else {
|
|
96
|
+
routes.push({ method, path });
|
|
97
|
+
}
|
|
98
|
+
return original(path, handlerOrConfig);
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
app.get = wrapMethod('GET', originalGet);
|
|
103
|
+
app.post = wrapMethod('POST', originalPost);
|
|
104
|
+
app.put = wrapMethod('PUT', originalPut);
|
|
105
|
+
app.delete = wrapMethod('DELETE', originalDelete);
|
|
106
|
+
app.patch = wrapMethod('PATCH', originalPatch);
|
|
107
|
+
|
|
108
|
+
const basePath = resolvedConfig.path!;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Authentication middleware for playground routes
|
|
112
|
+
* Implements HTTP Basic Authentication when auth config is provided
|
|
113
|
+
*/
|
|
114
|
+
const authMiddleware = (ctx: any) => {
|
|
115
|
+
if (!resolvedConfig.auth) {
|
|
116
|
+
return true; // No auth required
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const authHeader = ctx.raw?.req?.headers?.authorization || ctx.headers?.get?.('authorization');
|
|
120
|
+
|
|
121
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const base64Credentials = authHeader.substring(6);
|
|
127
|
+
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
|
|
128
|
+
const [username, password] = credentials.split(':');
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
username === resolvedConfig.auth.username &&
|
|
132
|
+
password === resolvedConfig.auth.password
|
|
133
|
+
);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return false;
|
|
119
136
|
}
|
|
120
|
-
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns 401 Unauthorized response with WWW-Authenticate header
|
|
141
|
+
* This triggers the browser's native authentication dialog
|
|
142
|
+
*/
|
|
143
|
+
const unauthorizedResponse = () => {
|
|
144
|
+
return {
|
|
145
|
+
statusCode: 401,
|
|
146
|
+
headers: {
|
|
147
|
+
'WWW-Authenticate': 'Basic realm="API Playground"',
|
|
148
|
+
'Content-Type': 'text/plain'
|
|
149
|
+
},
|
|
150
|
+
body: 'Unauthorized'
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
originalGet(basePath, async (ctx: any) => {
|
|
155
|
+
// Check authentication
|
|
156
|
+
if (!authMiddleware(ctx)) {
|
|
157
|
+
return unauthorizedResponse();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!detectedBaseUrl && ctx.raw?.req) {
|
|
161
|
+
const req = ctx.raw.req;
|
|
162
|
+
const protocol = req.headers['x-forwarded-proto'] || 'http';
|
|
163
|
+
const host = req.headers.host || 'localhost:3000';
|
|
164
|
+
detectedBaseUrl = `${protocol}://${host}`;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
statusCode: 200,
|
|
168
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
169
|
+
body: generatePlaygroundHTML(resolvedConfig, detectedBaseUrl)
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
originalGet(basePath + '/api/routes', async (ctx: Context) => {
|
|
174
|
+
// Check authentication
|
|
175
|
+
if (!authMiddleware(ctx)) {
|
|
176
|
+
return unauthorizedResponse();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Combine intercepted routes with routes from router (for file-based routing)
|
|
180
|
+
const allRoutes = getAllRoutes(routes, appInstance, basePath);
|
|
181
|
+
|
|
182
|
+
const routesData = allRoutes.map(r => ({
|
|
183
|
+
method: r.method,
|
|
184
|
+
path: r.path,
|
|
185
|
+
summary: r.meta?.summary || generateSummary(r.method as HTTPMethod, r.path),
|
|
186
|
+
description: r.meta?.description,
|
|
187
|
+
tags: r.meta?.tags || [getTagFromPath(r.path)],
|
|
188
|
+
deprecated: r.meta?.deprecated,
|
|
189
|
+
responses: r.meta?.responses,
|
|
190
|
+
example: r.meta?.example,
|
|
191
|
+
schema: {
|
|
192
|
+
body: r.schema?.body ? zodToExample(r.schema.body) : null,
|
|
193
|
+
query: r.schema?.query ? zodToParams(r.schema.query) : null,
|
|
194
|
+
params: extractPathParams(r.path)
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
return ctx.json(routesData);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
121
201
|
}
|
|
122
202
|
|
|
123
203
|
/**
|
|
@@ -125,46 +205,46 @@ export function playground(config: PlaygroundConfig = {}): Plugin {
|
|
|
125
205
|
* This ensures file-based routes are also included
|
|
126
206
|
*/
|
|
127
207
|
function getAllRoutes(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
208
|
+
interceptedRoutes: StoredRoute[],
|
|
209
|
+
app: Application | null,
|
|
210
|
+
basePath: string
|
|
131
211
|
): StoredRoute[] {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
212
|
+
// Create a Set of already tracked route keys (method + path)
|
|
213
|
+
const trackedKeys = new Set(
|
|
214
|
+
interceptedRoutes.map(r => `${r.method}:${r.path}`)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Start with intercepted routes (filtered)
|
|
218
|
+
const allRoutes: StoredRoute[] = interceptedRoutes.filter(
|
|
219
|
+
r => !r.path.startsWith(basePath)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Get routes from router (includes file-based routes)
|
|
223
|
+
if (app) {
|
|
224
|
+
const routerRoutes = app.getRoutes() as Array<{
|
|
225
|
+
method: string;
|
|
226
|
+
path: string;
|
|
227
|
+
schema?: SchemaConfig;
|
|
228
|
+
meta?: RouteMeta;
|
|
229
|
+
}>;
|
|
230
|
+
|
|
231
|
+
for (const route of routerRoutes) {
|
|
232
|
+
const key = `${route.method}:${route.path}`;
|
|
233
|
+
|
|
234
|
+
// Skip if already tracked or is playground route
|
|
235
|
+
if (trackedKeys.has(key) || route.path.startsWith(basePath)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
allRoutes.push({
|
|
240
|
+
method: route.method as HTTPMethod,
|
|
241
|
+
path: route.path,
|
|
242
|
+
schema: route.schema,
|
|
243
|
+
meta: route.meta
|
|
244
|
+
});
|
|
166
245
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Sort routes by path for consistent ordering
|
|
249
|
+
return allRoutes.sort((a, b) => a.path.localeCompare(b.path));
|
|
170
250
|
}
|
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import { HTTPMethod, RouteMeta, SchemaConfig } from "../../core/types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the API Playground
|
|
5
|
+
*/
|
|
3
6
|
export interface PlaygroundConfig {
|
|
7
|
+
/** URL path where the playground will be accessible (default: '/playground') */
|
|
4
8
|
path?: string;
|
|
9
|
+
|
|
10
|
+
/** Title displayed in the playground UI (default: 'API Playground') */
|
|
5
11
|
title?: string;
|
|
12
|
+
|
|
13
|
+
/** Color theme for the playground interface (default: 'dark') */
|
|
6
14
|
theme?: 'dark' | 'light';
|
|
15
|
+
|
|
16
|
+
/** Default HTTP headers to include in API requests */
|
|
7
17
|
defaultHeaders?: Record<string, string>;
|
|
18
|
+
|
|
19
|
+
/** Enable/disable request history tracking (default: true) */
|
|
8
20
|
enableHistory?: boolean;
|
|
21
|
+
|
|
22
|
+
/** Maximum number of requests to keep in history (default: 50) */
|
|
9
23
|
maxHistory?: number;
|
|
24
|
+
|
|
25
|
+
/** Enable/disable variable substitution in requests (default: true) */
|
|
10
26
|
enableVariables?: boolean;
|
|
27
|
+
|
|
28
|
+
/** Predefined variables for use in requests (e.g., {{baseUrl}}, {{token}}) */
|
|
11
29
|
variables?: Record<string, string>;
|
|
30
|
+
|
|
31
|
+
/** Enable only in development mode (default: true) */
|
|
32
|
+
developmentOnly?: boolean;
|
|
33
|
+
|
|
34
|
+
/** Basic authentication configuration to protect playground access */
|
|
35
|
+
auth?: {
|
|
36
|
+
/** Username for basic authentication */
|
|
37
|
+
username: string;
|
|
38
|
+
/** Password for basic authentication */
|
|
39
|
+
password: string;
|
|
40
|
+
};
|
|
12
41
|
}
|
|
13
42
|
|
|
14
43
|
|
|
@@ -149,47 +149,50 @@ export const ${middlewareName}Middleware: Middleware = async (ctx: Context, next
|
|
|
149
149
|
@Controller('/${baseName}')
|
|
150
150
|
export class ${className} {
|
|
151
151
|
@Get('/')
|
|
152
|
-
async getAll(
|
|
153
|
-
return
|
|
152
|
+
async getAll() {
|
|
153
|
+
return {
|
|
154
154
|
message: 'Get all ${baseName}',
|
|
155
155
|
data: [],
|
|
156
|
-
}
|
|
156
|
+
};
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
@Get('/:id')
|
|
160
160
|
async getById(ctx: Context) {
|
|
161
161
|
const { id } = ctx.params;
|
|
162
|
-
return
|
|
162
|
+
return {
|
|
163
163
|
message: \`Get ${baseName} by id: \${id}\`,
|
|
164
164
|
data: null,
|
|
165
|
-
}
|
|
165
|
+
};
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
@Post('/')
|
|
169
169
|
async create(ctx: Context) {
|
|
170
170
|
const body = await ctx.body();
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
data:
|
|
174
|
-
|
|
171
|
+
return {
|
|
172
|
+
status: 201,
|
|
173
|
+
data: {
|
|
174
|
+
message: 'Create ${baseName}',
|
|
175
|
+
data: body,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
@Put('/:id')
|
|
178
181
|
async update(ctx: Context) {
|
|
179
182
|
const { id } = ctx.params;
|
|
180
183
|
const body = await ctx.body();
|
|
181
|
-
return
|
|
184
|
+
return {
|
|
182
185
|
message: \`Update ${baseName}: \${id}\`,
|
|
183
186
|
data: body,
|
|
184
|
-
}
|
|
187
|
+
};
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
@Delete('/:id')
|
|
188
191
|
async delete(ctx: Context) {
|
|
189
192
|
const { id } = ctx.params;
|
|
190
|
-
return
|
|
193
|
+
return {
|
|
191
194
|
message: \`Delete ${baseName}: \${id}\`,
|
|
192
|
-
}
|
|
195
|
+
};
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
198
|
`;
|
|
@@ -271,18 +271,18 @@ app.listen(PORT, () => {
|
|
|
271
271
|
|
|
272
272
|
export const routes = new Router();
|
|
273
273
|
|
|
274
|
-
routes.get('/', async (
|
|
275
|
-
return
|
|
274
|
+
routes.get('/', async () => {
|
|
275
|
+
return {
|
|
276
276
|
message: 'Welcome to Nexus!',
|
|
277
277
|
version: '1.0.0',
|
|
278
|
-
}
|
|
278
|
+
};
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
-
routes.get('/health', async (
|
|
282
|
-
return
|
|
281
|
+
routes.get('/health', async () => {
|
|
282
|
+
return {
|
|
283
283
|
status: 'ok',
|
|
284
284
|
timestamp: new Date().toISOString(),
|
|
285
|
-
}
|
|
285
|
+
};
|
|
286
286
|
});
|
|
287
287
|
`;
|
|
288
288
|
}
|
|
@@ -320,21 +320,21 @@ import { userRoutes } from './users';
|
|
|
320
320
|
export const routes = new Router();
|
|
321
321
|
|
|
322
322
|
// Health check
|
|
323
|
-
routes.get('/health', async (
|
|
324
|
-
return
|
|
323
|
+
routes.get('/health', async () => {
|
|
324
|
+
return {
|
|
325
325
|
status: 'ok',
|
|
326
326
|
timestamp: new Date().toISOString(),
|
|
327
327
|
uptime: process.uptime(),
|
|
328
|
-
}
|
|
328
|
+
};
|
|
329
329
|
});
|
|
330
330
|
|
|
331
331
|
// API info
|
|
332
|
-
routes.get('/api', async (
|
|
333
|
-
return
|
|
332
|
+
routes.get('/api', async () => {
|
|
333
|
+
return {
|
|
334
334
|
name: 'API',
|
|
335
335
|
version: '1.0.0',
|
|
336
336
|
endpoints: ['/api/users', '/health'],
|
|
337
|
-
}
|
|
337
|
+
};
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
// Mount user routes
|
|
@@ -343,7 +343,7 @@ routes.group('/api/users', userRoutes);
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
private getUserRoutes(): string {
|
|
346
|
-
return `import { Router } from '@engjts/nexus';
|
|
346
|
+
return `import { Router, HttpError } from '@engjts/nexus';
|
|
347
347
|
import { UserService } from '../services/user.service';
|
|
348
348
|
import { createUserSchema, updateUserSchema } from '../validators/user.validator';
|
|
349
349
|
|
|
@@ -351,9 +351,9 @@ export const userRoutes = new Router();
|
|
|
351
351
|
const userService = new UserService();
|
|
352
352
|
|
|
353
353
|
// GET /api/users
|
|
354
|
-
userRoutes.get('/', async (
|
|
354
|
+
userRoutes.get('/', async () => {
|
|
355
355
|
const users = await userService.findAll();
|
|
356
|
-
return
|
|
356
|
+
return { users };
|
|
357
357
|
});
|
|
358
358
|
|
|
359
359
|
// GET /api/users/:id
|
|
@@ -362,10 +362,10 @@ userRoutes.get('/:id', async (ctx) => {
|
|
|
362
362
|
const user = await userService.findById(id);
|
|
363
363
|
|
|
364
364
|
if (!user) {
|
|
365
|
-
|
|
365
|
+
throw new HttpError(404, 'User not found');
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
return
|
|
368
|
+
return { user };
|
|
369
369
|
});
|
|
370
370
|
|
|
371
371
|
// POST /api/users
|
|
@@ -374,7 +374,7 @@ userRoutes.post('/', async (ctx) => {
|
|
|
374
374
|
const validated = createUserSchema.parse(body);
|
|
375
375
|
|
|
376
376
|
const user = await userService.create(validated);
|
|
377
|
-
return
|
|
377
|
+
return { status: 201, data: { user } };
|
|
378
378
|
});
|
|
379
379
|
|
|
380
380
|
// PUT /api/users/:id
|
|
@@ -386,10 +386,10 @@ userRoutes.put('/:id', async (ctx) => {
|
|
|
386
386
|
const user = await userService.update(id, validated);
|
|
387
387
|
|
|
388
388
|
if (!user) {
|
|
389
|
-
|
|
389
|
+
throw new HttpError(404, 'User not found');
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
return
|
|
392
|
+
return { user };
|
|
393
393
|
});
|
|
394
394
|
|
|
395
395
|
// DELETE /api/users/:id
|
|
@@ -398,22 +398,22 @@ userRoutes.delete('/:id', async (ctx) => {
|
|
|
398
398
|
const deleted = await userService.delete(id);
|
|
399
399
|
|
|
400
400
|
if (!deleted) {
|
|
401
|
-
|
|
401
|
+
throw new HttpError(404, 'User not found');
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
return
|
|
404
|
+
return { message: 'User deleted successfully' };
|
|
405
405
|
});
|
|
406
406
|
`;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
private getAuthMiddleware(): string {
|
|
410
|
-
return `import { Middleware, Context } from '@engjts/nexus';
|
|
410
|
+
return `import { Middleware, Context, HttpError } from '@engjts/nexus';
|
|
411
411
|
|
|
412
412
|
export const authMiddleware: Middleware = async (ctx: Context, next) => {
|
|
413
413
|
const authHeader = ctx.headers.get('authorization');
|
|
414
414
|
|
|
415
415
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
416
|
-
|
|
416
|
+
throw new HttpError(401, 'Unauthorized');
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
const token = authHeader.slice(7);
|
|
@@ -425,7 +425,7 @@ export const authMiddleware: Middleware = async (ctx: Context, next) => {
|
|
|
425
425
|
|
|
426
426
|
return next();
|
|
427
427
|
} catch (error) {
|
|
428
|
-
|
|
428
|
+
throw new HttpError(401, 'Invalid token');
|
|
429
429
|
}
|
|
430
430
|
};
|
|
431
431
|
`;
|