@superdangerous/app-framework 4.9.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 (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,305 @@
1
+ /**
2
+ * OpenAPI Documentation Middleware
3
+ * Provides automatic API documentation generation
4
+ */
5
+
6
+ import swaggerJsdoc from "swagger-jsdoc";
7
+ import swaggerUi from "swagger-ui-express";
8
+ import { Express } from "express";
9
+
10
+ export interface OpenAPIConfig {
11
+ title: string;
12
+ version: string;
13
+ description?: string;
14
+ servers?: Array<{
15
+ url: string;
16
+ description?: string;
17
+ }>;
18
+ contact?: {
19
+ name?: string;
20
+ email?: string;
21
+ url?: string;
22
+ };
23
+ license?: {
24
+ name: string;
25
+ url?: string;
26
+ };
27
+ tags?: Array<{
28
+ name: string;
29
+ description?: string;
30
+ }>;
31
+ }
32
+
33
+ /**
34
+ * Generate OpenAPI specification
35
+ * @param config OpenAPI configuration
36
+ * @param apis Array of file patterns for API routes
37
+ */
38
+ export function generateOpenAPISpec(
39
+ config: OpenAPIConfig,
40
+ apis: string[],
41
+ ): object {
42
+ const options: swaggerJsdoc.Options = {
43
+ definition: {
44
+ openapi: "3.0.0",
45
+ info: {
46
+ title: config.title,
47
+ version: config.version,
48
+ description: config.description,
49
+ contact: config.contact,
50
+ license: config.license,
51
+ },
52
+ servers: config.servers || [
53
+ {
54
+ url: "http://localhost:3000",
55
+ description: "Development server",
56
+ },
57
+ ],
58
+ tags: config.tags,
59
+ components: {
60
+ schemas: {
61
+ ApiResponse: {
62
+ type: "object",
63
+ properties: {
64
+ success: {
65
+ type: "boolean",
66
+ description: "Indicates if the request was successful",
67
+ },
68
+ data: {
69
+ type: "object",
70
+ description: "Response data payload",
71
+ },
72
+ error: {
73
+ type: "object",
74
+ properties: {
75
+ code: {
76
+ type: "string",
77
+ description: "Error code",
78
+ },
79
+ message: {
80
+ type: "string",
81
+ description: "Error message",
82
+ },
83
+ details: {
84
+ type: "object",
85
+ description: "Additional error details",
86
+ },
87
+ },
88
+ },
89
+ message: {
90
+ type: "string",
91
+ description: "Optional success message",
92
+ },
93
+ metadata: {
94
+ type: "object",
95
+ properties: {
96
+ timestamp: {
97
+ type: "string",
98
+ format: "date-time",
99
+ description: "Response timestamp",
100
+ },
101
+ version: {
102
+ type: "string",
103
+ description: "API version",
104
+ },
105
+ pagination: {
106
+ type: "object",
107
+ properties: {
108
+ page: { type: "integer" },
109
+ limit: { type: "integer" },
110
+ total: { type: "integer" },
111
+ hasMore: { type: "boolean" },
112
+ },
113
+ },
114
+ },
115
+ },
116
+ },
117
+ },
118
+ ValidationError: {
119
+ type: "object",
120
+ properties: {
121
+ field: {
122
+ type: "string",
123
+ description: "Field that failed validation",
124
+ },
125
+ message: {
126
+ type: "string",
127
+ description: "Validation error message",
128
+ },
129
+ },
130
+ },
131
+ ErrorResponse: {
132
+ type: "object",
133
+ properties: {
134
+ success: {
135
+ type: "boolean",
136
+ enum: [false],
137
+ },
138
+ error: {
139
+ type: "object",
140
+ properties: {
141
+ code: { type: "string" },
142
+ message: { type: "string" },
143
+ details: { type: "object" },
144
+ },
145
+ required: ["code", "message"],
146
+ },
147
+ },
148
+ required: ["success", "error"],
149
+ },
150
+ },
151
+ securitySchemes: {
152
+ sessionAuth: {
153
+ type: "apiKey",
154
+ in: "cookie",
155
+ name: "connect.sid",
156
+ description: "Session-based authentication",
157
+ },
158
+ bearerAuth: {
159
+ type: "http",
160
+ scheme: "bearer",
161
+ bearerFormat: "JWT",
162
+ description: "JWT bearer token authentication",
163
+ },
164
+ },
165
+ responses: {
166
+ BadRequest: {
167
+ description: "Bad request",
168
+ content: {
169
+ "application/json": {
170
+ schema: {
171
+ $ref: "#/components/schemas/ErrorResponse",
172
+ },
173
+ },
174
+ },
175
+ },
176
+ Unauthorized: {
177
+ description: "Authentication required",
178
+ content: {
179
+ "application/json": {
180
+ schema: {
181
+ $ref: "#/components/schemas/ErrorResponse",
182
+ },
183
+ },
184
+ },
185
+ },
186
+ Forbidden: {
187
+ description: "Access denied",
188
+ content: {
189
+ "application/json": {
190
+ schema: {
191
+ $ref: "#/components/schemas/ErrorResponse",
192
+ },
193
+ },
194
+ },
195
+ },
196
+ NotFound: {
197
+ description: "Resource not found",
198
+ content: {
199
+ "application/json": {
200
+ schema: {
201
+ $ref: "#/components/schemas/ErrorResponse",
202
+ },
203
+ },
204
+ },
205
+ },
206
+ InternalError: {
207
+ description: "Internal server error",
208
+ content: {
209
+ "application/json": {
210
+ schema: {
211
+ $ref: "#/components/schemas/ErrorResponse",
212
+ },
213
+ },
214
+ },
215
+ },
216
+ },
217
+ },
218
+ },
219
+ apis,
220
+ };
221
+
222
+ return swaggerJsdoc(options);
223
+ }
224
+
225
+ /**
226
+ * Setup OpenAPI documentation UI
227
+ * @param app Express application
228
+ * @param config OpenAPI configuration
229
+ * @param apis Array of file patterns for API routes
230
+ * @param path Path to serve documentation (default: /api-docs)
231
+ */
232
+ export function setupOpenAPIDocumentation(
233
+ app: Express,
234
+ config: OpenAPIConfig,
235
+ apis: string[],
236
+ path: string = "/api-docs",
237
+ ): void {
238
+ const swaggerSpec = generateOpenAPISpec(config, apis);
239
+
240
+ // Serve OpenAPI spec as JSON
241
+ app.get(`${path}/spec.json`, (_req, res) => {
242
+ res.json(swaggerSpec);
243
+ });
244
+
245
+ // Serve Swagger UI
246
+ app.use(
247
+ path,
248
+ swaggerUi.serve,
249
+ swaggerUi.setup(swaggerSpec, {
250
+ customSiteTitle: `${config.title} - API Documentation`,
251
+ customfavIcon: "/favicon.ico",
252
+ customCss: `
253
+ .swagger-ui .topbar { display: none }
254
+ .swagger-ui .info { margin-bottom: 40px }
255
+ .swagger-ui .scheme-container { display: none }
256
+ `,
257
+ swaggerOptions: {
258
+ persistAuthorization: true,
259
+ displayRequestDuration: true,
260
+ docExpansion: "none",
261
+ filter: true,
262
+ showExtensions: true,
263
+ showCommonExtensions: true,
264
+ displayOperationId: false,
265
+ },
266
+ }),
267
+ );
268
+ }
269
+
270
+ /**
271
+ * Example JSDoc annotation for OpenAPI
272
+ *
273
+ * @swagger
274
+ * /api/resource:
275
+ * get:
276
+ * summary: Get all resources
277
+ * tags: [Resources]
278
+ * responses:
279
+ * 200:
280
+ * description: Successful response
281
+ * content:
282
+ * application/json:
283
+ * schema:
284
+ * $ref: '#/components/schemas/ApiResponse'
285
+ * 500:
286
+ * $ref: '#/components/responses/InternalError'
287
+ */
288
+
289
+ /**
290
+ * OpenAPI operation decorator (for future TypeScript decorator support)
291
+ */
292
+ export function ApiOperation(operation: any) {
293
+ return function (
294
+ target: any,
295
+ propertyKey: string,
296
+ descriptor: PropertyDescriptor,
297
+ ) {
298
+ // Store OpenAPI metadata for later processing
299
+ if (!target._openapi) {
300
+ target._openapi = {};
301
+ }
302
+ target._openapi[propertyKey] = operation;
303
+ return descriptor;
304
+ };
305
+ }
@@ -0,0 +1,92 @@
1
+ import type { NextFunction, Request, Response } from "express";
2
+ import { randomUUID } from "crypto";
3
+ import { createLogger } from "../core/logger.js";
4
+
5
+ export interface RequestLoggingOptions {
6
+ logger?: any;
7
+ /**
8
+ * Paths to skip logging for (commonly health checks or metrics)
9
+ */
10
+ skipPaths?: Array<string | RegExp>;
11
+ /**
12
+ * Whether to log the request payload (truncated)
13
+ */
14
+ logPayload?: boolean;
15
+ /**
16
+ * Maximum number of characters from the payload to log
17
+ */
18
+ maxPayloadLength?: number;
19
+ }
20
+
21
+ const defaultSkips = [/^\/health/, /^\/api\/health/];
22
+
23
+ function shouldSkip(path: string, patterns: Array<string | RegExp>) {
24
+ return patterns.some((pattern) => {
25
+ if (pattern instanceof RegExp) {
26
+ return pattern.test(path);
27
+ }
28
+ return path.startsWith(pattern);
29
+ });
30
+ }
31
+
32
+ function safePayload(body: unknown, maxLength: number): string | undefined {
33
+ if (!body || typeof body !== "object") return undefined;
34
+ const serialized = JSON.stringify(body);
35
+ if (serialized.length <= maxLength) return serialized;
36
+ return `${serialized.slice(0, maxLength)}…`;
37
+ }
38
+
39
+ /**
40
+ * Express middleware that assigns a request ID and logs completion.
41
+ * Uses the framework logger so request logs land in the same files as the app.
42
+ */
43
+ export function createRequestLoggingMiddleware(
44
+ options: RequestLoggingOptions = {},
45
+ ) {
46
+ const logger =
47
+ options.logger ||
48
+ // Avoid forcing the consumer to initialize logging before use
49
+ createLogger("http");
50
+ const skipPatterns = options.skipPaths || defaultSkips;
51
+ const logPayload = options.logPayload ?? false;
52
+ const maxPayloadLength = options.maxPayloadLength ?? 2000;
53
+
54
+ return function requestLogging(
55
+ req: Request,
56
+ res: Response,
57
+ next: NextFunction,
58
+ ) {
59
+ const startedAt = process.hrtime.bigint();
60
+ const requestId = (req as any).requestId || randomUUID();
61
+ (req as any).requestId = requestId;
62
+ res.locals.requestId = requestId;
63
+ res.setHeader("x-request-id", requestId);
64
+
65
+ res.on("finish", () => {
66
+ if (shouldSkip(req.path, skipPatterns)) return;
67
+
68
+ const durationMs =
69
+ Number(process.hrtime.bigint() - startedAt) / 1_000_000;
70
+
71
+ // Prefer the richer logRequest helper if available
72
+ if (typeof logger.logRequest === "function") {
73
+ logger.logRequest(req, res, Number(durationMs.toFixed(2)));
74
+ return;
75
+ }
76
+
77
+ logger.info("Request completed", {
78
+ method: req.method,
79
+ path: req.originalUrl || req.url,
80
+ status: res.statusCode,
81
+ durationMs: Number(durationMs.toFixed(2)),
82
+ requestId,
83
+ userAgent: req.get("user-agent"),
84
+ payload: logPayload
85
+ ? safePayload(req.body, maxPayloadLength)
86
+ : undefined,
87
+ });
88
+ });
89
+
90
+ next();
91
+ };
92
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Session Configuration Middleware
3
+ * Standardized session management for Express applications
4
+ */
5
+
6
+ import session from "express-session";
7
+ import { Express } from "express";
8
+ import { createLogger } from "../core/index.js";
9
+
10
+ let logger: any; // Will be initialized when needed
11
+
12
+ function ensureLogger() {
13
+ if (!logger) {
14
+ logger = createLogger("SessionMiddleware");
15
+ }
16
+ return logger;
17
+ }
18
+
19
+ export interface SessionConfig {
20
+ secret?: string;
21
+ name?: string;
22
+ maxAge?: number;
23
+ secure?: boolean;
24
+ sameSite?: "lax" | "strict" | "none";
25
+ trustProxy?: boolean;
26
+ resave?: boolean;
27
+ saveUninitialized?: boolean;
28
+ rolling?: boolean;
29
+ store?: "memory" | session.Store;
30
+ }
31
+
32
+ /**
33
+ * Configure session middleware with sensible defaults
34
+ */
35
+ export function configureSession(app: Express, config: SessionConfig = {}) {
36
+ const {
37
+ secret = process.env.SESSION_SECRET || "change-this-secret-in-production",
38
+ name = "app.sid",
39
+ maxAge = 3600000, // 1 hour
40
+ secure = process.env.NODE_ENV === "production",
41
+ sameSite = "lax",
42
+ trustProxy = true,
43
+ resave = false,
44
+ saveUninitialized = false,
45
+ rolling = true,
46
+ store = "memory",
47
+ } = config;
48
+
49
+ // Warn about insecure settings in production
50
+ if (process.env.NODE_ENV === "production") {
51
+ if (secret === "change-this-secret-in-production") {
52
+ ensureLogger().warn(
53
+ "Using default session secret in production! Set SESSION_SECRET environment variable.",
54
+ );
55
+ }
56
+ if (store === "memory") {
57
+ ensureLogger().warn(
58
+ "Using memory session store in production! Consider using Redis or another persistent store.",
59
+ );
60
+ }
61
+ if (!secure) {
62
+ ensureLogger().warn(
63
+ "Session cookies are not secure in production! Consider enabling secure cookies.",
64
+ );
65
+ }
66
+ }
67
+
68
+ // Trust proxy if configured
69
+ if (trustProxy) {
70
+ app.set("trust proxy", 1);
71
+ }
72
+
73
+ // Suppress MemoryStore warning in production
74
+ let originalWarn: typeof console.warn | undefined;
75
+ if (process.env.NODE_ENV === "production" && store === "memory") {
76
+ originalWarn = console.warn;
77
+ console.warn = (msg: string, ...args: any[]) => {
78
+ if (typeof msg === "string" && !msg.includes("MemoryStore")) {
79
+ originalWarn!(msg, ...args);
80
+ }
81
+ };
82
+ }
83
+
84
+ const sessionConfig: session.SessionOptions = {
85
+ secret,
86
+ name,
87
+ resave,
88
+ saveUninitialized,
89
+ rolling,
90
+ cookie: {
91
+ secure,
92
+ httpOnly: true,
93
+ maxAge,
94
+ sameSite,
95
+ path: "/",
96
+ },
97
+ };
98
+
99
+ // Add store if provided
100
+ if (store !== "memory") {
101
+ sessionConfig.store = store;
102
+ }
103
+
104
+ // Apply middleware
105
+ app.use(session(sessionConfig));
106
+
107
+ // Restore console.warn if it was modified
108
+ if (originalWarn) {
109
+ // Restore after a brief delay to ensure session is initialized
110
+ setTimeout(() => {
111
+ console.warn = originalWarn!;
112
+ }, 100);
113
+ }
114
+
115
+ if (
116
+ process.env.NODE_ENV === "development" &&
117
+ process.env.LOG_LEVEL?.toLowerCase() === "debug"
118
+ ) {
119
+ ensureLogger().debug("Session middleware configured", {
120
+ name,
121
+ secure,
122
+ sameSite,
123
+ maxAge: `${maxAge / 1000}s`,
124
+ store: store === "memory" ? "memory" : "custom",
125
+ });
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Create a Redis session store (OPTIONAL)
131
+ *
132
+ * Redis is an optional dependency for persistent session storage in production.
133
+ * If Redis packages are not installed, the framework gracefully falls back to memory store.
134
+ *
135
+ * To enable Redis sessions:
136
+ * 1. Install optional dependencies: `npm install connect-redis redis`
137
+ * 2. Set REDIS_URL environment variable (optional, defaults to redis://localhost:6379)
138
+ * 3. Use createRedisStore() in your session configuration
139
+ *
140
+ * For desktop applications with single users, memory store is usually sufficient.
141
+ *
142
+ * @param redisUrl - Optional Redis connection URL (defaults to REDIS_URL env var or redis://localhost:6379)
143
+ * @returns Redis store instance or 'memory' string on failure
144
+ */
145
+ export async function createRedisStore(redisUrl?: string) {
146
+ try {
147
+ // @ts-expect-error - Optional dependency
148
+ const RedisStore = (await import(/* @vite-ignore */ "connect-redis"))
149
+ .default;
150
+ // @ts-expect-error - Optional dependency
151
+ const { createClient } = await import(/* @vite-ignore */ "redis");
152
+
153
+ const redisClient = createClient({
154
+ url: redisUrl || process.env.REDIS_URL || "redis://localhost:6379",
155
+ });
156
+
157
+ await redisClient.connect();
158
+
159
+ ensureLogger().info("Redis session store connected");
160
+
161
+ return new RedisStore({
162
+ client: redisClient,
163
+ prefix: "session:",
164
+ });
165
+ } catch (_error: any) {
166
+ ensureLogger().error("Failed to create Redis store:", _error.message);
167
+ ensureLogger().warn("Falling back to memory store");
168
+ return "memory" as const;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Session utilities
174
+ */
175
+ export const sessionUtils = {
176
+ /**
177
+ * Regenerate session ID (for security after login)
178
+ */
179
+ regenerateSession(req: any): Promise<void> {
180
+ return new Promise((resolve, reject) => {
181
+ req.session.regenerate((err: any) => {
182
+ if (err) {
183
+ ensureLogger().error("Failed to regenerate session:", err);
184
+ reject(err);
185
+ } else {
186
+ resolve();
187
+ }
188
+ });
189
+ });
190
+ },
191
+
192
+ /**
193
+ * Save session explicitly
194
+ */
195
+ saveSession(req: any): Promise<void> {
196
+ return new Promise((resolve, reject) => {
197
+ req.session.save((err: any) => {
198
+ if (err) {
199
+ ensureLogger().error("Failed to save session:", err);
200
+ reject(err);
201
+ } else {
202
+ resolve();
203
+ }
204
+ });
205
+ });
206
+ },
207
+
208
+ /**
209
+ * Destroy session
210
+ */
211
+ destroySession(req: any): Promise<void> {
212
+ return new Promise((resolve, reject) => {
213
+ req.session.destroy((err: any) => {
214
+ if (err) {
215
+ ensureLogger().error("Failed to destroy session:", err);
216
+ reject(err);
217
+ } else {
218
+ resolve();
219
+ }
220
+ });
221
+ });
222
+ },
223
+
224
+ /**
225
+ * Touch session to extend expiry
226
+ */
227
+ touchSession(req: any): void {
228
+ if (req.session) {
229
+ req.session.touch();
230
+ }
231
+ },
232
+ };
233
+
234
+ export default {
235
+ configureSession,
236
+ createRedisStore,
237
+ sessionUtils,
238
+ };