@noony-serverless/core 0.3.4 → 0.4.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.
Files changed (51) hide show
  1. package/build/core/containerPool.d.ts +129 -26
  2. package/build/core/containerPool.js +213 -68
  3. package/build/core/handler.d.ts +2 -2
  4. package/build/core/handler.js +6 -12
  5. package/build/core/index.d.ts +1 -0
  6. package/build/core/index.js +1 -0
  7. package/build/core/logger.d.ts +89 -1
  8. package/build/core/logger.js +136 -5
  9. package/build/core/telemetry/config.d.ts +331 -0
  10. package/build/core/telemetry/config.js +153 -0
  11. package/build/core/telemetry/index.d.ts +22 -0
  12. package/build/core/telemetry/index.js +45 -0
  13. package/build/core/telemetry/provider.d.ts +203 -0
  14. package/build/core/telemetry/provider.js +3 -0
  15. package/build/core/telemetry/providers/console-provider.d.ts +54 -0
  16. package/build/core/telemetry/providers/console-provider.js +124 -0
  17. package/build/core/telemetry/providers/index.d.ts +10 -0
  18. package/build/core/telemetry/providers/index.js +19 -0
  19. package/build/core/telemetry/providers/noop-provider.d.ts +51 -0
  20. package/build/core/telemetry/providers/noop-provider.js +67 -0
  21. package/build/core/telemetry/providers/opentelemetry-provider.d.ts +102 -0
  22. package/build/core/telemetry/providers/opentelemetry-provider.js +342 -0
  23. package/build/middlewares/dependencyInjectionMiddleware.d.ts +16 -8
  24. package/build/middlewares/dependencyInjectionMiddleware.js +31 -11
  25. package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +1 -1
  26. package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
  27. package/build/middlewares/guards/guards/FastAuthGuard.js +3 -2
  28. package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +7 -9
  29. package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
  30. package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
  31. package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
  32. package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
  33. package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
  34. package/build/middlewares/guards/services/FastUserContextService.d.ts +11 -32
  35. package/build/middlewares/index.d.ts +1 -0
  36. package/build/middlewares/index.js +1 -0
  37. package/build/middlewares/openTelemetryMiddleware.d.ts +162 -0
  38. package/build/middlewares/openTelemetryMiddleware.js +359 -0
  39. package/build/middlewares/rateLimitingMiddleware.js +16 -5
  40. package/build/utils/container.utils.js +4 -1
  41. package/build/utils/fastify-wrapper.d.ts +74 -0
  42. package/build/utils/fastify-wrapper.js +175 -0
  43. package/build/utils/index.d.ts +4 -0
  44. package/build/utils/index.js +23 -1
  45. package/build/utils/otel.helper.d.ts +122 -0
  46. package/build/utils/otel.helper.js +258 -0
  47. package/build/utils/pubsub-trace.utils.d.ts +102 -0
  48. package/build/utils/pubsub-trace.utils.js +155 -0
  49. package/build/utils/wrapper-utils.d.ts +177 -0
  50. package/build/utils/wrapper-utils.js +236 -0
  51. package/package.json +61 -2
@@ -0,0 +1,177 @@
1
+ import type { HttpFunction } from '@google-cloud/functions-framework';
2
+ import type { Request, Response } from 'express';
3
+ import { Handler } from '../core/handler';
4
+ /**
5
+ * Create an HttpFunction wrapper for a Noony handler
6
+ *
7
+ * Wraps a Noony handler into a Google Cloud Functions `HttpFunction` for production deployment.
8
+ * This pattern ensures proper initialization, error handling, and prevents double responses.
9
+ *
10
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
11
+ * @param functionName - Name for error logging purposes
12
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
13
+ * Uses singleton pattern to prevent re-initialization on warm starts
14
+ * @returns HttpFunction compatible with `@google-cloud/functions-framework`
15
+ *
16
+ * @remarks
17
+ * This function ensures:
18
+ * - Dependencies are initialized before handler execution (optimized for cold/warm starts)
19
+ * - Errors are caught and returned as proper HTTP responses
20
+ * - Response is not sent twice (`headersSent` check)
21
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
22
+ * - Real errors return 500 with generic message for security
23
+ *
24
+ * @example
25
+ * Creating and registering Cloud Functions:
26
+ * ```typescript
27
+ * import { http } from '@google-cloud/functions-framework';
28
+ * import { createHttpFunction } from '@noony-serverless/core';
29
+ * import { loginHandler } from './handlers/auth.handlers';
30
+ *
31
+ * // Initialize dependencies once per cold start
32
+ * let initialized = false;
33
+ * async function initializeDependencies(): Promise<void> {
34
+ * if (initialized) return;
35
+ * const db = await databaseService.connect();
36
+ * await initializeServices(db);
37
+ * initialized = true;
38
+ * }
39
+ *
40
+ * // Create and register function
41
+ * const loginFunction = createHttpFunction(
42
+ * loginHandler,
43
+ * 'login',
44
+ * initializeDependencies
45
+ * );
46
+ * http('login', loginFunction);
47
+ * export const login = loginFunction;
48
+ * ```
49
+ *
50
+ * @example
51
+ * Execution flow:
52
+ * ```
53
+ * HTTP Request → createHttpFunction wrapper
54
+ * │
55
+ * ▼
56
+ * initializeDependencies() (only on cold start)
57
+ * │
58
+ * ▼
59
+ * noonyHandler.execute(req, res)
60
+ * │
61
+ * ├─── errorHandler()
62
+ * ├─── authMiddleware()
63
+ * ├─── requirePermission()
64
+ * ├─── bodyValidator()
65
+ * ├─── ResponseWrapperMiddleware
66
+ * └─── Controller function
67
+ * │
68
+ * ▼
69
+ * HTTP Response
70
+ * ```
71
+ *
72
+ * @see {@link wrapNoonyHandler} for Express integration (local development)
73
+ */
74
+ export declare function createHttpFunction(noonyHandler: Handler<unknown>, functionName: string, initializeDependencies: () => Promise<void>): HttpFunction;
75
+ /**
76
+ * Wrap a Noony handler for use with Express routing
77
+ *
78
+ * Wraps a Noony handler into an Express route handler for local development environments.
79
+ * This pattern enables running all endpoints through a single Express app with standard
80
+ * Express routing, middleware, and error handling.
81
+ *
82
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
83
+ * @param functionName - Name for error logging purposes
84
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
85
+ * Uses singleton pattern to prevent re-initialization across requests
86
+ * @returns Express route handler compatible with Express Router: `(req: Request, res: Response) => Promise<void>`
87
+ *
88
+ * @remarks
89
+ * This wrapper ensures:
90
+ * - Dependencies are initialized before handler execution (singleton pattern for efficiency)
91
+ * - Noony handlers work seamlessly with Express routing and middleware
92
+ * - Errors are caught and returned as proper HTTP responses
93
+ * - Response is not sent twice (`headersSent` check)
94
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
95
+ * - Real errors return 500 with generic message for security
96
+ *
97
+ * **Differences from createHttpFunction:**
98
+ *
99
+ * | Aspect | createHttpFunction | wrapNoonyHandler |
100
+ * |--------|-------------------|------------------|
101
+ * | **Use case** | Production deployment | Local development |
102
+ * | **Framework** | Cloud Functions Framework | Express |
103
+ * | **Return type** | `HttpFunction` | Express handler |
104
+ * | **Registration** | `http('name', fn)` | `app.get('/path', fn)` |
105
+ * | **Deployment** | Individual functions | Single Express app |
106
+ *
107
+ * @example
108
+ * Creating Express app with multiple routes:
109
+ * ```typescript
110
+ * import express, { Express } from 'express';
111
+ * import { wrapNoonyHandler } from '@noony-serverless/core';
112
+ * import { loginHandler, logoutHandler, getConfigHandler } from './handlers';
113
+ *
114
+ * // Initialize dependencies once per app startup
115
+ * let initialized = false;
116
+ * async function initializeDependencies(): Promise<void> {
117
+ * if (initialized) return;
118
+ * const db = await databaseService.connect();
119
+ * await initializeServices(db);
120
+ * initialized = true;
121
+ * }
122
+ *
123
+ * function createExpressApp(): Express {
124
+ * const app = express();
125
+ *
126
+ * // Global Express middleware
127
+ * app.use(cors());
128
+ * app.use(express.json());
129
+ *
130
+ * // Health check (no DB required)
131
+ * app.get('/health', (_req, res) => {
132
+ * res.json({ success: true, data: { status: 'healthy' } });
133
+ * });
134
+ *
135
+ * // Auth routes
136
+ * app.post('/api/auth/login', wrapNoonyHandler(loginHandler, 'login', initializeDependencies));
137
+ * app.post('/api/auth/logout', wrapNoonyHandler(logoutHandler, 'logout', initializeDependencies));
138
+ *
139
+ * // Config routes
140
+ * app.get('/api/config', wrapNoonyHandler(getConfigHandler, 'getConfig', initializeDependencies));
141
+ *
142
+ * // 404 handler
143
+ * app.use((_req, res) => {
144
+ * res.status(404).json({
145
+ * success: false,
146
+ * error: { code: 'NOT_FOUND', message: 'Endpoint not found' }
147
+ * });
148
+ * });
149
+ *
150
+ * return app;
151
+ * }
152
+ *
153
+ * // Start server
154
+ * const app = createExpressApp();
155
+ * const PORT = process.env.PORT || 3000;
156
+ * app.listen(PORT, () => {
157
+ * console.log(`Server running on port ${PORT}`);
158
+ * });
159
+ * ```
160
+ *
161
+ * @example
162
+ * Express routing with path parameters:
163
+ * ```typescript
164
+ * const app = express();
165
+ *
166
+ * // Routes with path parameters work seamlessly
167
+ * app.get('/api/users/:userId', wrapNoonyHandler(getUserHandler, 'getUser', initializeDependencies));
168
+ * app.patch('/api/config/sections/:sectionId', wrapNoonyHandler(updateSectionHandler, 'updateSection', initializeDependencies));
169
+ * app.delete('/api/config/sections/:sectionId', wrapNoonyHandler(deleteSectionHandler, 'deleteSection', initializeDependencies));
170
+ *
171
+ * // Path parameters available in Noony handler via context.req.params
172
+ * ```
173
+ *
174
+ * @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
175
+ */
176
+ export declare function wrapNoonyHandler(noonyHandler: Handler<unknown>, functionName: string, initializeDependencies: () => Promise<void>): (req: Request, res: Response) => Promise<void>;
177
+ //# sourceMappingURL=wrapper-utils.d.ts.map
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createHttpFunction = createHttpFunction;
4
+ exports.wrapNoonyHandler = wrapNoonyHandler;
5
+ const logger_1 = require("../core/logger");
6
+ /**
7
+ * Create an HttpFunction wrapper for a Noony handler
8
+ *
9
+ * Wraps a Noony handler into a Google Cloud Functions `HttpFunction` for production deployment.
10
+ * This pattern ensures proper initialization, error handling, and prevents double responses.
11
+ *
12
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
13
+ * @param functionName - Name for error logging purposes
14
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
15
+ * Uses singleton pattern to prevent re-initialization on warm starts
16
+ * @returns HttpFunction compatible with `@google-cloud/functions-framework`
17
+ *
18
+ * @remarks
19
+ * This function ensures:
20
+ * - Dependencies are initialized before handler execution (optimized for cold/warm starts)
21
+ * - Errors are caught and returned as proper HTTP responses
22
+ * - Response is not sent twice (`headersSent` check)
23
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
24
+ * - Real errors return 500 with generic message for security
25
+ *
26
+ * @example
27
+ * Creating and registering Cloud Functions:
28
+ * ```typescript
29
+ * import { http } from '@google-cloud/functions-framework';
30
+ * import { createHttpFunction } from '@noony-serverless/core';
31
+ * import { loginHandler } from './handlers/auth.handlers';
32
+ *
33
+ * // Initialize dependencies once per cold start
34
+ * let initialized = false;
35
+ * async function initializeDependencies(): Promise<void> {
36
+ * if (initialized) return;
37
+ * const db = await databaseService.connect();
38
+ * await initializeServices(db);
39
+ * initialized = true;
40
+ * }
41
+ *
42
+ * // Create and register function
43
+ * const loginFunction = createHttpFunction(
44
+ * loginHandler,
45
+ * 'login',
46
+ * initializeDependencies
47
+ * );
48
+ * http('login', loginFunction);
49
+ * export const login = loginFunction;
50
+ * ```
51
+ *
52
+ * @example
53
+ * Execution flow:
54
+ * ```
55
+ * HTTP Request → createHttpFunction wrapper
56
+ * │
57
+ * ▼
58
+ * initializeDependencies() (only on cold start)
59
+ * │
60
+ * ▼
61
+ * noonyHandler.execute(req, res)
62
+ * │
63
+ * ├─── errorHandler()
64
+ * ├─── authMiddleware()
65
+ * ├─── requirePermission()
66
+ * ├─── bodyValidator()
67
+ * ├─── ResponseWrapperMiddleware
68
+ * └─── Controller function
69
+ * │
70
+ * ▼
71
+ * HTTP Response
72
+ * ```
73
+ *
74
+ * @see {@link wrapNoonyHandler} for Express integration (local development)
75
+ */
76
+ function createHttpFunction(noonyHandler, functionName, initializeDependencies) {
77
+ return async (req, res) => {
78
+ try {
79
+ // Ensure dependencies are initialized
80
+ await initializeDependencies();
81
+ // Execute Noony handler (runs middleware chain + controller)
82
+ // Cast is safe because Handler.execute internally adapts GCP Request/Response
83
+ await noonyHandler.execute(req, res);
84
+ }
85
+ catch (error) {
86
+ // Only handle errors if they're real errors (not RESPONSE_SENT markers)
87
+ if (error instanceof Error && error.message === 'RESPONSE_SENT') {
88
+ return;
89
+ }
90
+ logger_1.logger.error(`${functionName} function error`, {
91
+ error: error instanceof Error ? error.message : 'Unknown error',
92
+ stack: error instanceof Error ? error.stack : undefined,
93
+ });
94
+ // Graceful error handling - only send if headers not already sent
95
+ if (!res.headersSent) {
96
+ res.status(500).json({
97
+ success: false,
98
+ error: {
99
+ code: 'INTERNAL_SERVER_ERROR',
100
+ message: 'An unexpected error occurred',
101
+ },
102
+ });
103
+ }
104
+ }
105
+ };
106
+ }
107
+ /**
108
+ * Wrap a Noony handler for use with Express routing
109
+ *
110
+ * Wraps a Noony handler into an Express route handler for local development environments.
111
+ * This pattern enables running all endpoints through a single Express app with standard
112
+ * Express routing, middleware, and error handling.
113
+ *
114
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
115
+ * @param functionName - Name for error logging purposes
116
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
117
+ * Uses singleton pattern to prevent re-initialization across requests
118
+ * @returns Express route handler compatible with Express Router: `(req: Request, res: Response) => Promise<void>`
119
+ *
120
+ * @remarks
121
+ * This wrapper ensures:
122
+ * - Dependencies are initialized before handler execution (singleton pattern for efficiency)
123
+ * - Noony handlers work seamlessly with Express routing and middleware
124
+ * - Errors are caught and returned as proper HTTP responses
125
+ * - Response is not sent twice (`headersSent` check)
126
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
127
+ * - Real errors return 500 with generic message for security
128
+ *
129
+ * **Differences from createHttpFunction:**
130
+ *
131
+ * | Aspect | createHttpFunction | wrapNoonyHandler |
132
+ * |--------|-------------------|------------------|
133
+ * | **Use case** | Production deployment | Local development |
134
+ * | **Framework** | Cloud Functions Framework | Express |
135
+ * | **Return type** | `HttpFunction` | Express handler |
136
+ * | **Registration** | `http('name', fn)` | `app.get('/path', fn)` |
137
+ * | **Deployment** | Individual functions | Single Express app |
138
+ *
139
+ * @example
140
+ * Creating Express app with multiple routes:
141
+ * ```typescript
142
+ * import express, { Express } from 'express';
143
+ * import { wrapNoonyHandler } from '@noony-serverless/core';
144
+ * import { loginHandler, logoutHandler, getConfigHandler } from './handlers';
145
+ *
146
+ * // Initialize dependencies once per app startup
147
+ * let initialized = false;
148
+ * async function initializeDependencies(): Promise<void> {
149
+ * if (initialized) return;
150
+ * const db = await databaseService.connect();
151
+ * await initializeServices(db);
152
+ * initialized = true;
153
+ * }
154
+ *
155
+ * function createExpressApp(): Express {
156
+ * const app = express();
157
+ *
158
+ * // Global Express middleware
159
+ * app.use(cors());
160
+ * app.use(express.json());
161
+ *
162
+ * // Health check (no DB required)
163
+ * app.get('/health', (_req, res) => {
164
+ * res.json({ success: true, data: { status: 'healthy' } });
165
+ * });
166
+ *
167
+ * // Auth routes
168
+ * app.post('/api/auth/login', wrapNoonyHandler(loginHandler, 'login', initializeDependencies));
169
+ * app.post('/api/auth/logout', wrapNoonyHandler(logoutHandler, 'logout', initializeDependencies));
170
+ *
171
+ * // Config routes
172
+ * app.get('/api/config', wrapNoonyHandler(getConfigHandler, 'getConfig', initializeDependencies));
173
+ *
174
+ * // 404 handler
175
+ * app.use((_req, res) => {
176
+ * res.status(404).json({
177
+ * success: false,
178
+ * error: { code: 'NOT_FOUND', message: 'Endpoint not found' }
179
+ * });
180
+ * });
181
+ *
182
+ * return app;
183
+ * }
184
+ *
185
+ * // Start server
186
+ * const app = createExpressApp();
187
+ * const PORT = process.env.PORT || 3000;
188
+ * app.listen(PORT, () => {
189
+ * console.log(`Server running on port ${PORT}`);
190
+ * });
191
+ * ```
192
+ *
193
+ * @example
194
+ * Express routing with path parameters:
195
+ * ```typescript
196
+ * const app = express();
197
+ *
198
+ * // Routes with path parameters work seamlessly
199
+ * app.get('/api/users/:userId', wrapNoonyHandler(getUserHandler, 'getUser', initializeDependencies));
200
+ * app.patch('/api/config/sections/:sectionId', wrapNoonyHandler(updateSectionHandler, 'updateSection', initializeDependencies));
201
+ * app.delete('/api/config/sections/:sectionId', wrapNoonyHandler(deleteSectionHandler, 'deleteSection', initializeDependencies));
202
+ *
203
+ * // Path parameters available in Noony handler via context.req.params
204
+ * ```
205
+ *
206
+ * @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
207
+ */
208
+ function wrapNoonyHandler(noonyHandler, functionName, initializeDependencies) {
209
+ return async (req, res) => {
210
+ try {
211
+ // Ensure dependencies are initialized
212
+ await initializeDependencies();
213
+ // Execute Noony handler with Express req/res (cast to generic types)
214
+ await noonyHandler.executeGeneric(req, res);
215
+ }
216
+ catch (error) {
217
+ if (error instanceof Error && error.message === 'RESPONSE_SENT') {
218
+ return;
219
+ }
220
+ logger_1.logger.error(`${functionName} handler error`, {
221
+ error: error instanceof Error ? error.message : 'Unknown error',
222
+ stack: error instanceof Error ? error.stack : undefined,
223
+ });
224
+ if (!res.headersSent) {
225
+ res.status(500).json({
226
+ success: false,
227
+ error: {
228
+ code: 'INTERNAL_SERVER_ERROR',
229
+ message: 'An unexpected error occurred',
230
+ },
231
+ });
232
+ }
233
+ }
234
+ };
235
+ }
236
+ //# sourceMappingURL=wrapper-utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noony-serverless/core",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -71,5 +71,64 @@
71
71
  "ts-jest": "^29.1.1",
72
72
  "typescript": "^5.3.3"
73
73
  },
74
- "private": false
74
+ "peerDependencies": {
75
+ "@fastify/otel": "^1.0.0",
76
+ "@google-cloud/opentelemetry-cloud-trace-propagator": "^0.21.0",
77
+ "@opentelemetry/api": "^1.9.0",
78
+ "@opentelemetry/auto-instrumentations-node": "^0.200.0",
79
+ "@opentelemetry/core": "^1.28.0",
80
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0",
81
+ "@opentelemetry/exporter-trace-otlp-http": "^0.205.0",
82
+ "@opentelemetry/instrumentation-http": "^0.200.0",
83
+ "@opentelemetry/resources": "^2.0.0",
84
+ "@opentelemetry/sdk-node": "^2.0.0",
85
+ "@opentelemetry/semantic-conventions": "^1.28.0",
86
+ "dd-trace": "^5.0.0",
87
+ "newrelic": "^12.0.0"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "@google-cloud/opentelemetry-cloud-trace-propagator": {
91
+ "optional": true
92
+ },
93
+ "@opentelemetry/api": {
94
+ "optional": true
95
+ },
96
+ "@opentelemetry/core": {
97
+ "optional": true
98
+ },
99
+ "@opentelemetry/sdk-node": {
100
+ "optional": true
101
+ },
102
+ "@opentelemetry/resources": {
103
+ "optional": true
104
+ },
105
+ "@opentelemetry/semantic-conventions": {
106
+ "optional": true
107
+ },
108
+ "@opentelemetry/instrumentation-http": {
109
+ "optional": true
110
+ },
111
+ "@opentelemetry/exporter-trace-otlp-http": {
112
+ "optional": true
113
+ },
114
+ "@opentelemetry/exporter-metrics-otlp-http": {
115
+ "optional": true
116
+ },
117
+ "@opentelemetry/auto-instrumentations-node": {
118
+ "optional": true
119
+ },
120
+ "@fastify/otel": {
121
+ "optional": true
122
+ },
123
+ "newrelic": {
124
+ "optional": true
125
+ },
126
+ "dd-trace": {
127
+ "optional": true
128
+ }
129
+ },
130
+ "private": false,
131
+ "optionalDependencies": {
132
+ "@google-cloud/opentelemetry-cloud-trace-propagator": "^0.21.0"
133
+ }
75
134
  }