te.js 2.1.0 → 2.1.2

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.
Files changed (70) hide show
  1. package/README.md +197 -196
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -7
  6. package/auto-docs/docs-llm/prompts.js +222 -222
  7. package/auto-docs/docs-llm/provider.js +132 -132
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/cors/index.js +71 -0
  20. package/database/index.js +165 -165
  21. package/database/mongodb.js +146 -146
  22. package/database/redis.js +201 -201
  23. package/docs/README.md +36 -36
  24. package/docs/ammo.md +362 -362
  25. package/docs/api-reference.md +490 -490
  26. package/docs/auto-docs.md +216 -216
  27. package/docs/cli.md +152 -152
  28. package/docs/configuration.md +275 -275
  29. package/docs/database.md +390 -390
  30. package/docs/error-handling.md +438 -438
  31. package/docs/file-uploads.md +333 -333
  32. package/docs/getting-started.md +214 -214
  33. package/docs/middleware.md +355 -355
  34. package/docs/rate-limiting.md +393 -393
  35. package/docs/routing.md +302 -302
  36. package/lib/llm/client.js +73 -0
  37. package/lib/llm/index.js +7 -0
  38. package/lib/llm/parse.js +89 -0
  39. package/package.json +64 -62
  40. package/rate-limit/algorithms/fixed-window.js +141 -141
  41. package/rate-limit/algorithms/sliding-window.js +147 -147
  42. package/rate-limit/algorithms/token-bucket.js +115 -115
  43. package/rate-limit/base.js +165 -165
  44. package/rate-limit/index.js +147 -147
  45. package/rate-limit/storage/base.js +104 -104
  46. package/rate-limit/storage/memory.js +101 -101
  47. package/rate-limit/storage/redis.js +88 -88
  48. package/server/ammo/body-parser.js +220 -220
  49. package/server/ammo/dispatch-helper.js +103 -103
  50. package/server/ammo/enhancer.js +57 -57
  51. package/server/ammo.js +454 -415
  52. package/server/endpoint.js +97 -74
  53. package/server/error.js +9 -9
  54. package/server/errors/code-context.js +125 -125
  55. package/server/errors/llm-error-service.js +140 -140
  56. package/server/files/helper.js +33 -33
  57. package/server/files/uploader.js +143 -143
  58. package/server/handler.js +158 -119
  59. package/server/target.js +185 -175
  60. package/server/targets/middleware-validator.js +22 -22
  61. package/server/targets/path-validator.js +21 -21
  62. package/server/targets/registry.js +160 -160
  63. package/server/targets/shoot-validator.js +21 -21
  64. package/te.js +428 -402
  65. package/utils/auto-register.js +17 -17
  66. package/utils/configuration.js +64 -64
  67. package/utils/errors-llm-config.js +84 -84
  68. package/utils/request-logger.js +43 -43
  69. package/utils/status-codes.js +82 -82
  70. package/utils/tejas-entrypoint-html.js +18 -18
package/server/handler.js CHANGED
@@ -1,119 +1,158 @@
1
- import { env } from 'tej-env';
2
- import TejLogger from 'tej-logger';
3
- import logHttpRequest from '../utils/request-logger.js';
4
-
5
- import Ammo from './ammo.js';
6
- import TejError from './error.js';
7
- import targetRegistry from './targets/registry.js';
8
-
9
- const errorLogger = new TejLogger('Tejas.Exception');
10
-
11
- /**
12
- * Executes the middleware and handler chain for a given target.
13
- *
14
- * @param {Object} target - The target endpoint object.
15
- * @param {Ammo} ammo - The Ammo instance containing request and response objects.
16
- * @returns {Promise<void>} A promise that resolves when the chain execution is complete.
17
- */
18
- const executeChain = async (target, ammo) => {
19
- let i = 0;
20
-
21
- const chain = targetRegistry.globalMiddlewares.concat(
22
- target.getMiddlewares(),
23
- );
24
- chain.push(target.getHandler());
25
-
26
- const next = async () => {
27
- // Check if response has already been sent (e.g., by passport.authenticate redirect)
28
- if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
29
- return;
30
- }
31
-
32
- const middleware = chain[i];
33
- i++;
34
-
35
- const args =
36
- middleware.length === 3 ? [ammo.req, ammo.res, next] : [ammo, next];
37
-
38
- try {
39
- const result = await middleware(...args);
40
-
41
- // Check again after middleware execution (passport might have redirected)
42
- if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
43
- return;
44
- }
45
-
46
- // If middleware returned a promise that resolved, continue chain
47
- if (result && typeof result.then === 'function') {
48
- await result;
49
- // Check one more time after promise resolution
50
- if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
51
- return;
52
- }
53
- }
54
- } catch (err) {
55
- // Only handle error if response hasn't been sent
56
- if (!ammo.res.headersSent && !ammo.res.writableEnded && !ammo.res.finished) {
57
- await errorHandler(ammo, err);
58
- }
59
- }
60
- };
61
-
62
- await next();
63
- };
64
-
65
- /**
66
- * Handles errors: optional logging (log.exceptions) and sending the response via ammo.throw(err).
67
- * One mechanism ammo.throw takes care of everything (no separate "log then send").
68
- * When errors.llm.enabled, framework-caught errors get the same LLM-inferred response as explicit ammo.throw().
69
- * When ammo.throw() returns a Promise (LLM path), waits for it to complete.
70
- *
71
- * @param {Ammo} ammo - The Ammo instance containing request and response objects.
72
- * @param {Error} err - The error object to handle.
73
- * @returns {Promise<void>}
74
- */
75
- const errorHandler = async (ammo, err) => {
76
- if (env('LOG_EXCEPTIONS')) errorLogger.error(err);
77
-
78
- const result = ammo.throw(err);
79
- if (result != null && typeof result.then === 'function') {
80
- await result;
81
- }
82
- };
83
-
84
- /**
85
- * Main request handler function.
86
- *
87
- * @param {http.IncomingMessage} req - The HTTP request object.
88
- * @param {http.ServerResponse} res - The HTTP response object.
89
- * @returns {Promise<void>} A promise that resolves when the request handling is complete.
90
- */
91
- const handler = async (req, res) => {
92
- const url = req.url.split('?')[0];
93
- const match = targetRegistry.aim(url);
94
- const ammo = new Ammo(req, res);
95
-
96
- try {
97
- if (match && match.target) {
98
- await ammo.enhance();
99
-
100
- // Add route parameters to ammo.payload
101
- if (match.params && Object.keys(match.params).length > 0) {
102
- Object.assign(ammo.payload, match.params);
103
- }
104
-
105
- if (env('LOG_HTTP_REQUESTS')) logHttpRequest(ammo);
106
- await executeChain(match.target, ammo);
107
- } else {
108
- if (req.url === '/') {
109
- ammo.defaultEntry();
110
- } else {
111
- await errorHandler(ammo, new TejError(404, `URL not found: ${url}`));
112
- }
113
- }
114
- } catch (err) {
115
- await errorHandler(ammo, err);
116
- }
117
- };
118
-
119
- export default handler;
1
+ import { env } from 'tej-env';
2
+ import TejLogger from 'tej-logger';
3
+ import logHttpRequest from '../utils/request-logger.js';
4
+
5
+ import Ammo from './ammo.js';
6
+ import TejError from './error.js';
7
+ import targetRegistry from './targets/registry.js';
8
+
9
+ const errorLogger = new TejLogger('Tejas.Exception');
10
+
11
+ const DEFAULT_ALLOWED_METHODS = [
12
+ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS',
13
+ ];
14
+
15
+ /**
16
+ * Returns the set of allowed HTTP methods (configurable via allowedMethods in tejas.config.json or ALLOWEDMETHODS env).
17
+ * @returns {Set<string>}
18
+ */
19
+ const getAllowedMethods = () => {
20
+ const raw = env('ALLOWEDMETHODS');
21
+ if (raw == null) return new Set(DEFAULT_ALLOWED_METHODS);
22
+ const arr = Array.isArray(raw)
23
+ ? raw
24
+ : (typeof raw === 'string' ? raw.split(',').map((s) => s.trim()) : []);
25
+ const normalized = arr.map((m) => String(m).toUpperCase()).filter(Boolean);
26
+ return normalized.length > 0 ? new Set(normalized) : new Set(DEFAULT_ALLOWED_METHODS);
27
+ };
28
+
29
+ /**
30
+ * Executes the middleware and handler chain for a given target.
31
+ *
32
+ * @param {Object} target - The target endpoint object.
33
+ * @param {Ammo} ammo - The Ammo instance containing request and response objects.
34
+ * @returns {Promise<void>} A promise that resolves when the chain execution is complete.
35
+ */
36
+ const executeChain = async (target, ammo) => {
37
+ let i = 0;
38
+
39
+ const chain = targetRegistry.globalMiddlewares.concat(
40
+ target.getMiddlewares(),
41
+ );
42
+ chain.push(target.getHandler());
43
+
44
+ const next = async () => {
45
+ // Check if response has already been sent (e.g., by passport.authenticate redirect)
46
+ if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
47
+ return;
48
+ }
49
+
50
+ const middleware = chain[i];
51
+ i++;
52
+
53
+ const args =
54
+ middleware.length === 3 ? [ammo.req, ammo.res, next] : [ammo, next];
55
+
56
+ try {
57
+ const result = await middleware(...args);
58
+
59
+ // Check again after middleware execution (passport might have redirected)
60
+ if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
61
+ return;
62
+ }
63
+
64
+ // If middleware returned a promise that resolved, continue chain
65
+ if (result && typeof result.then === 'function') {
66
+ await result;
67
+ // Check one more time after promise resolution
68
+ if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
69
+ return;
70
+ }
71
+ }
72
+ } catch (err) {
73
+ // Only handle error if response hasn't been sent
74
+ if (!ammo.res.headersSent && !ammo.res.writableEnded && !ammo.res.finished) {
75
+ await errorHandler(ammo, err);
76
+ }
77
+ }
78
+ };
79
+
80
+ await next();
81
+ };
82
+
83
+ /**
84
+ * Handles errors: optional logging (log.exceptions) and sending the response via ammo.throw(err).
85
+ * One mechanism ammo.throw — takes care of everything (no separate "log then send").
86
+ * When errors.llm.enabled, framework-caught errors get the same LLM-inferred response as explicit ammo.throw().
87
+ * When ammo.throw() returns a Promise (LLM path), waits for it to complete.
88
+ *
89
+ * @param {Ammo} ammo - The Ammo instance containing request and response objects.
90
+ * @param {Error} err - The error object to handle.
91
+ * @returns {Promise<void>}
92
+ */
93
+ const errorHandler = async (ammo, err) => {
94
+ if (env('LOG_EXCEPTIONS')) errorLogger.error(err);
95
+
96
+ const result = ammo.throw(err);
97
+ if (result != null && typeof result.then === 'function') {
98
+ await result;
99
+ }
100
+ };
101
+
102
+ /**
103
+ * Main request handler function.
104
+ *
105
+ * @param {http.IncomingMessage} req - The HTTP request object.
106
+ * @param {http.ServerResponse} res - The HTTP response object.
107
+ * @returns {Promise<void>} A promise that resolves when the request handling is complete.
108
+ */
109
+ const handler = async (req, res) => {
110
+ const allowedMethods = getAllowedMethods();
111
+ const method = req.method ? String(req.method).toUpperCase() : '';
112
+ if (!method || !allowedMethods.has(method)) {
113
+ res.writeHead(405, {
114
+ 'Content-Type': 'text/plain',
115
+ Allow: [...allowedMethods].join(', '),
116
+ });
117
+ res.end('Method Not Allowed');
118
+ return;
119
+ }
120
+
121
+ const url = req.url.split('?')[0];
122
+ const match = targetRegistry.aim(url);
123
+ const ammo = new Ammo(req, res);
124
+
125
+ try {
126
+ if (match && match.target) {
127
+ await ammo.enhance();
128
+
129
+ const allowedMethods = match.target.getMethods();
130
+ if (allowedMethods != null && allowedMethods.length > 0) {
131
+ const method = ammo.method && String(ammo.method).toUpperCase();
132
+ if (!method || !allowedMethods.includes(method)) {
133
+ ammo.res.setHeader('Allow', allowedMethods.join(', '));
134
+ await errorHandler(ammo, new TejError(405, 'Method Not Allowed'));
135
+ return;
136
+ }
137
+ }
138
+
139
+ // Add route parameters to ammo.payload
140
+ if (match.params && Object.keys(match.params).length > 0) {
141
+ Object.assign(ammo.payload, match.params);
142
+ }
143
+
144
+ if (env('LOG_HTTP_REQUESTS')) logHttpRequest(ammo);
145
+ await executeChain(match.target, ammo);
146
+ } else {
147
+ if (req.url === '/') {
148
+ ammo.defaultEntry();
149
+ } else {
150
+ await errorHandler(ammo, new TejError(404, `URL not found: ${url}`));
151
+ }
152
+ }
153
+ } catch (err) {
154
+ await errorHandler(ammo, err);
155
+ }
156
+ };
157
+
158
+ export default handler;
package/server/target.js CHANGED
@@ -1,175 +1,185 @@
1
- import TejLogger from 'tej-logger';
2
-
3
- import isMiddlewareValid from './targets/middleware-validator.js';
4
- import Endpoint from './endpoint.js';
5
-
6
- import targetRegistry from './targets/registry.js';
7
-
8
- const logger = new TejLogger('Target');
9
-
10
- /**
11
- * Target class represents a base routing configuration for endpoints. Think of it as router in express.
12
- * It provides functionality to set base paths, add middleware, and register endpoints.
13
- *
14
- * @class
15
- * @example
16
- * // Create a new target for user-related endpoints
17
- * const userTarget = new Target('/user');
18
- *
19
- * // Add middleware that applies to all user endpoints
20
- * userTarget.midair(authMiddleware, loggingMiddleware);
21
- *
22
- * // Register endpoints
23
- * userTarget.register('/profile', (ammo) => {
24
- * // Handle GET /user/profile
25
- * ammo.fire({ name: 'John Doe' });
26
- * });
27
- *
28
- * userTarget.register('/settings', authMiddleware, (ammo) => {
29
- * // Handle GET /user/settings with additional auth middleware
30
- * ammo.fire({ theme: 'dark' });
31
- * });
32
- */
33
- class Target {
34
- /**
35
- * Creates a new Target instance.
36
- *
37
- * @param {string} [base=''] - The base path for all endpoints registered under this target.
38
- * Must start with '/' if provided.
39
- * @example
40
- * const apiTarget = new Target('/api');
41
- * const userTarget = new Target('/user');
42
- */
43
- constructor(base = '') {
44
- this.base = base;
45
- this.targetMiddlewares = [];
46
- }
47
-
48
- /**
49
- * Sets the base path for the target.
50
- *
51
- * @param {string} base - The base path to set. Must start with '/'.
52
- * @returns {void}
53
- * @example
54
- * const target = new Target();
55
- * target.base('/api/v1');
56
- */
57
- base(base) {
58
- if (!base || !base.startsWith('/')) return;
59
- this.base = base;
60
- }
61
-
62
- /**
63
- * Adds middleware functions to the target.
64
- * These middleware functions will be applied to all endpoints registered under this target.
65
- *
66
- * @param {...Function} middlewares - One or more middleware functions to add.
67
- * @returns {void}
68
- * @example
69
- * // Add authentication middleware to all endpoints
70
- * target.midair(authMiddleware);
71
- *
72
- * // Add multiple middleware functions
73
- * target.midair(loggingMiddleware, errorHandler, rateLimiter);
74
- */
75
- midair() {
76
- if (!arguments) return;
77
- const middlewares = [...arguments];
78
-
79
- const validMiddlewares = middlewares.filter(isMiddlewareValid);
80
- this.targetMiddlewares = this.targetMiddlewares.concat(validMiddlewares);
81
- }
82
-
83
- /**
84
- * Registers a new endpoint under this target.
85
- *
86
- * @param {string} path - The path for the endpoint, relative to the base path.
87
- * @param {...Function} [middlewares] - Optional middleware functions specific to this endpoint.
88
- * @param {Function} shoot - The handler function for the endpoint.
89
- * @returns {void}
90
- * @throws {Error} If there's an error registering the endpoint.
91
- * @example
92
- * // Register a simple endpoint
93
- * target.register('/hello', (ammo) => {
94
- * ammo.fire({ message: 'Hello World' });
95
- * });
96
- *
97
- * // Register an endpoint with specific middleware
98
- * target.register('/protected', authMiddleware, (ammo) => {
99
- * ammo.fire({ data: 'Protected data' });
100
- * });
101
- *
102
- * // Register an endpoint with multiple middleware
103
- * target.register('/api/data',
104
- * authMiddleware,
105
- * rateLimiter,
106
- * (ammo) => {
107
- * ammo.fire({ data: 'Rate limited data' });
108
- * }
109
- * );
110
- */
111
- register() {
112
- const args = Array.from(arguments);
113
- if (args.length < 2) {
114
- logger.error('register(path, [...middlewares], handler) requires at least path and handler. Skipping.');
115
- return;
116
- }
117
-
118
- const path = args[0];
119
- const shoot = args[args.length - 1];
120
-
121
- if (typeof path !== 'string') {
122
- logger.error(`register() path must be a string, got ${typeof path}. Skipping.`);
123
- return;
124
- }
125
- if (typeof shoot !== 'function') {
126
- logger.error(`register() last argument (handler) must be a function, got ${typeof shoot}. Skipping.`);
127
- return;
128
- }
129
-
130
- const second = args[1];
131
- const isPlainObject = (v) =>
132
- typeof v === 'object' &&
133
- v !== null &&
134
- !Array.isArray(v) &&
135
- (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
136
- const isMetadataObject = isPlainObject(second);
137
-
138
- let middlewares;
139
- let metadata = null;
140
- if (isMetadataObject && args.length >= 3) {
141
- metadata = second;
142
- middlewares = args.slice(2, -1);
143
- } else {
144
- middlewares = args.slice(1, -1);
145
- }
146
-
147
- try {
148
- const endpoint = new Endpoint();
149
- endpoint.setPath(this.base, path);
150
- if (!endpoint.getPath()) {
151
- logger.error(`Invalid path for endpoint "${path}". Skipping.`);
152
- return;
153
- }
154
- endpoint.setMiddlewares(middlewares);
155
- endpoint.setHandler(shoot);
156
- if (!endpoint.getHandler()) {
157
- logger.error(`Invalid handler for endpoint "${path}". Skipping.`);
158
- return;
159
- }
160
- if (metadata !== null) {
161
- endpoint.setMetadata(metadata);
162
- }
163
- const group = targetRegistry.getCurrentSourceGroup();
164
- if (group != null) {
165
- endpoint.setGroup(group);
166
- }
167
-
168
- targetRegistry.targets.push(endpoint);
169
- } catch (error) {
170
- logger.error(`Error registering target ${path}: ${error.message}`);
171
- }
172
- }
173
- }
174
-
175
- export default Target;
1
+ import TejLogger from 'tej-logger';
2
+
3
+ import isMiddlewareValid from './targets/middleware-validator.js';
4
+ import Endpoint from './endpoint.js';
5
+
6
+ import targetRegistry from './targets/registry.js';
7
+
8
+ const logger = new TejLogger('Target');
9
+
10
+ /**
11
+ * Target class represents a base routing configuration for endpoints. Think of it as router in express.
12
+ * It provides functionality to set base paths, add middleware, and register endpoints.
13
+ *
14
+ * @class
15
+ * @example
16
+ * // Create a new target for user-related endpoints
17
+ * const userTarget = new Target('/user');
18
+ *
19
+ * // Add middleware that applies to all user endpoints
20
+ * userTarget.midair(authMiddleware, loggingMiddleware);
21
+ *
22
+ * // Register endpoints
23
+ * userTarget.register('/profile', (ammo) => {
24
+ * // Handle GET /user/profile
25
+ * ammo.fire({ name: 'John Doe' });
26
+ * });
27
+ *
28
+ * userTarget.register('/settings', authMiddleware, (ammo) => {
29
+ * // Handle GET /user/settings with additional auth middleware
30
+ * ammo.fire({ theme: 'dark' });
31
+ * });
32
+ */
33
+ class Target {
34
+ /**
35
+ * Creates a new Target instance.
36
+ *
37
+ * @param {string} [base=''] - The base path for all endpoints registered under this target.
38
+ * Must start with '/' if provided.
39
+ * @example
40
+ * const apiTarget = new Target('/api');
41
+ * const userTarget = new Target('/user');
42
+ */
43
+ constructor(base = '') {
44
+ this.base = base;
45
+ this.targetMiddlewares = [];
46
+ }
47
+
48
+ /**
49
+ * Sets the base path for the target.
50
+ *
51
+ * @param {string} base - The base path to set. Must start with '/'.
52
+ * @returns {void}
53
+ * @example
54
+ * const target = new Target();
55
+ * target.base('/api/v1');
56
+ */
57
+ base(base) {
58
+ if (!base || !base.startsWith('/')) return;
59
+ this.base = base;
60
+ }
61
+
62
+ /**
63
+ * Adds middleware functions to the target.
64
+ * These middleware functions will be applied to all endpoints registered under this target.
65
+ *
66
+ * @param {...Function} middlewares - One or more middleware functions to add.
67
+ * @returns {void}
68
+ * @example
69
+ * // Add authentication middleware to all endpoints
70
+ * target.midair(authMiddleware);
71
+ *
72
+ * // Add multiple middleware functions
73
+ * target.midair(loggingMiddleware, errorHandler, rateLimiter);
74
+ */
75
+ midair() {
76
+ if (!arguments) return;
77
+ const middlewares = [...arguments];
78
+
79
+ const validMiddlewares = middlewares.filter(isMiddlewareValid);
80
+ this.targetMiddlewares = this.targetMiddlewares.concat(validMiddlewares);
81
+ }
82
+
83
+ /**
84
+ * Registers a new endpoint under this target.
85
+ *
86
+ * @param {string} path - The path for the endpoint, relative to the base path.
87
+ * @param {Object} [metadata] - Optional metadata. If the second argument is a plain object, it is treated as metadata (e.g. { methods: ['GET', 'POST'] }). When `methods` is set, the framework returns 405 for other HTTP methods before the handler runs. HEAD is allowed automatically when GET is in the list.
88
+ * @param {...Function} [middlewares] - Optional middleware functions specific to this endpoint.
89
+ * @param {Function} shoot - The handler function for the endpoint.
90
+ * @returns {void}
91
+ * @throws {Error} If there's an error registering the endpoint.
92
+ * @example
93
+ * // Register a simple endpoint
94
+ * target.register('/hello', (ammo) => {
95
+ * ammo.fire({ message: 'Hello World' });
96
+ * });
97
+ *
98
+ * // Register with allowed methods (405 + Allow header for others)
99
+ * target.register('/users', { methods: ['GET', 'POST'] }, (ammo) => {
100
+ * if (ammo.GET) return ammo.fire(userService.list());
101
+ * if (ammo.POST) return ammo.fire(201, userService.create(ammo.payload));
102
+ * });
103
+ *
104
+ * // Register an endpoint with specific middleware
105
+ * target.register('/protected', authMiddleware, (ammo) => {
106
+ * ammo.fire({ data: 'Protected data' });
107
+ * });
108
+ *
109
+ * // Register an endpoint with multiple middleware
110
+ * target.register('/api/data',
111
+ * authMiddleware,
112
+ * rateLimiter,
113
+ * (ammo) => {
114
+ * ammo.fire({ data: 'Rate limited data' });
115
+ * }
116
+ * );
117
+ */
118
+ register() {
119
+ const args = Array.from(arguments);
120
+ if (args.length < 2) {
121
+ logger.error('register(path, [...middlewares], handler) requires at least path and handler. Skipping.');
122
+ return;
123
+ }
124
+
125
+ const path = args[0];
126
+ const shoot = args[args.length - 1];
127
+
128
+ if (typeof path !== 'string') {
129
+ logger.error(`register() path must be a string, got ${typeof path}. Skipping.`);
130
+ return;
131
+ }
132
+ if (typeof shoot !== 'function') {
133
+ logger.error(`register() last argument (handler) must be a function, got ${typeof shoot}. Skipping.`);
134
+ return;
135
+ }
136
+
137
+ const second = args[1];
138
+ const isPlainObject = (v) =>
139
+ typeof v === 'object' &&
140
+ v !== null &&
141
+ !Array.isArray(v) &&
142
+ (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
143
+ const isMetadataObject = isPlainObject(second);
144
+
145
+ let middlewares;
146
+ let metadata = null;
147
+ if (isMetadataObject && args.length >= 3) {
148
+ metadata = second;
149
+ middlewares = args.slice(2, -1);
150
+ } else {
151
+ middlewares = args.slice(1, -1);
152
+ }
153
+
154
+ try {
155
+ const endpoint = new Endpoint();
156
+ endpoint.setPath(this.base, path);
157
+ if (!endpoint.getPath()) {
158
+ logger.error(`Invalid path for endpoint "${path}". Skipping.`);
159
+ return;
160
+ }
161
+ endpoint.setMiddlewares(middlewares);
162
+ endpoint.setHandler(shoot);
163
+ if (!endpoint.getHandler()) {
164
+ logger.error(`Invalid handler for endpoint "${path}". Skipping.`);
165
+ return;
166
+ }
167
+ if (metadata !== null) {
168
+ endpoint.setMetadata(metadata);
169
+ if (Array.isArray(metadata.methods) && metadata.methods.length > 0) {
170
+ endpoint.setMethods(metadata.methods);
171
+ }
172
+ }
173
+ const group = targetRegistry.getCurrentSourceGroup();
174
+ if (group != null) {
175
+ endpoint.setGroup(group);
176
+ }
177
+
178
+ targetRegistry.targets.push(endpoint);
179
+ } catch (error) {
180
+ logger.error(`Error registering target ${path}: ${error.message}`);
181
+ }
182
+ }
183
+ }
184
+
185
+ export default Target;