@medyll/idae-api 0.137.0 → 0.139.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 (47) hide show
  1. package/README.md +61 -0
  2. package/dist/__tests__/README.md +583 -0
  3. package/dist/__tests__/fixtures/mockData.d.ts +219 -0
  4. package/dist/__tests__/fixtures/mockData.js +243 -0
  5. package/dist/__tests__/helpers/testUtils.d.ts +153 -0
  6. package/dist/__tests__/helpers/testUtils.js +328 -0
  7. package/dist/client/IdaeApiClient.d.ts +7 -0
  8. package/dist/client/IdaeApiClient.js +15 -2
  9. package/dist/client/IdaeApiClientCollection.d.ts +17 -9
  10. package/dist/client/IdaeApiClientCollection.js +32 -11
  11. package/dist/client/IdaeApiClientConfig.d.ts +2 -0
  12. package/dist/client/IdaeApiClientConfig.js +10 -2
  13. package/dist/client/IdaeApiClientRequest.js +30 -15
  14. package/dist/config/routeDefinitions.d.ts +8 -0
  15. package/dist/config/routeDefinitions.js +63 -15
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +10 -0
  18. package/dist/openApi/redoc.html +12 -0
  19. package/dist/openApi/swagger-ui.html +23 -0
  20. package/dist/server/IdaeApi.d.ts +15 -0
  21. package/dist/server/IdaeApi.js +94 -22
  22. package/dist/server/engine/requestDatabaseManager.d.ts +1 -1
  23. package/dist/server/engine/requestDatabaseManager.js +6 -10
  24. package/dist/server/engine/routeManager.d.ts +1 -0
  25. package/dist/server/engine/routeManager.js +33 -17
  26. package/dist/server/middleware/README.md +46 -0
  27. package/dist/server/middleware/authMiddleware.d.ts +63 -0
  28. package/dist/server/middleware/authMiddleware.js +121 -12
  29. package/dist/server/middleware/authorizationMiddleware.d.ts +18 -0
  30. package/dist/server/middleware/authorizationMiddleware.js +38 -0
  31. package/dist/server/middleware/databaseMiddleware.d.ts +6 -1
  32. package/dist/server/middleware/databaseMiddleware.js +111 -6
  33. package/dist/server/middleware/docsMiddleware.d.ts +13 -0
  34. package/dist/server/middleware/docsMiddleware.js +30 -0
  35. package/dist/server/middleware/healthMiddleware.d.ts +15 -0
  36. package/dist/server/middleware/healthMiddleware.js +19 -0
  37. package/dist/server/middleware/mcpMiddleware.d.ts +10 -0
  38. package/dist/server/middleware/mcpMiddleware.js +14 -0
  39. package/dist/server/middleware/openApiMiddleware.d.ts +7 -0
  40. package/dist/server/middleware/openApiMiddleware.js +20 -0
  41. package/dist/server/middleware/tenantContextMiddleware.d.ts +25 -0
  42. package/dist/server/middleware/tenantContextMiddleware.js +31 -0
  43. package/dist/server/middleware/validationLayer.d.ts +8 -0
  44. package/dist/server/middleware/validationLayer.js +23 -0
  45. package/dist/server/middleware/validationMiddleware.d.ts +16 -0
  46. package/dist/server/middleware/validationMiddleware.js +30 -0
  47. package/package.json +18 -4
@@ -1,56 +1,104 @@
1
1
  // packages\idae-api\src\lib\config\routeDefinitions.ts
2
+ import { z } from "zod";
3
+ import { createValidationMiddleware } from "../server/middleware/validationMiddleware.js";
4
+ const allowedQueryCommands = new Set([
5
+ "find",
6
+ "findById",
7
+ "findOne",
8
+ "create",
9
+ "update",
10
+ "updateWhere",
11
+ "deleteById",
12
+ "deleteWhere",
13
+ "transaction",
14
+ ]);
2
15
  export const routes = [
3
16
  {
4
17
  method: "get",
5
18
  path: "/:collectionName",
19
+ validation: {
20
+ paramsSchema: z.object({ collectionName: z.string().min(1) }),
21
+ querySchema: z.record(z.any()).optional(),
22
+ },
6
23
  handler: async (service, params, body, query) => service.find({ query }),
7
24
  },
8
25
  {
9
26
  method: "get",
10
27
  path: "/:collectionName/:id",
28
+ validation: {
29
+ paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
30
+ },
11
31
  handler: async (service, params) => service.findById(params.id),
12
32
  },
13
33
  {
14
34
  method: "post",
15
35
  path: "/:collectionName",
36
+ validation: {
37
+ paramsSchema: z.object({ collectionName: z.string().min(1) }),
38
+ bodySchema: z.record(z.any()),
39
+ },
16
40
  handler: async (service, params, body) => service.create(body),
17
41
  },
18
42
  {
19
43
  method: "put",
20
44
  path: "/:collectionName/:id",
45
+ validation: {
46
+ paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
47
+ bodySchema: z.record(z.any()),
48
+ },
21
49
  handler: async (service, params, body) => service.update(params.id, body),
22
50
  },
23
51
  {
24
52
  method: "delete",
25
53
  path: "/:collectionName/:id",
54
+ validation: {
55
+ paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
56
+ },
26
57
  handler: async (service, params) => service.deleteById(params.id),
27
58
  },
28
59
  {
29
60
  method: "delete",
30
61
  path: "/:collectionName",
62
+ validation: {
63
+ paramsSchema: z.object({ collectionName: z.string().min(1) }),
64
+ },
31
65
  handler: async (service, params) => service.deleteWhere(params),
32
66
  },
33
67
  {
34
- method: [
35
- "find",
36
- "findById",
37
- "findOne",
38
- "create",
39
- "update",
40
- "updateWhere",
41
- "deleteById",
42
- "deleteWhere",
43
- "transaction",
44
- ], // default method is then GET or OPTIONS (set further)
45
- path: "/query/:collectionName/:command/:parameters?",
68
+ method: "post",
69
+ path: "/query/:collectionName/:command/:parameters",
70
+ requiresAuth: true,
71
+ validation: {
72
+ paramsSchema: z.object({
73
+ collectionName: z.string().min(1),
74
+ command: z.string().min(1),
75
+ parameters: z.string().optional(),
76
+ }),
77
+ bodySchema: z.record(z.any()).optional(),
78
+ },
46
79
  handler: async (service, params, body) => {
47
- console.log(params.command, "params --- ", { body });
48
- return service?.[params.command]?.({ query: body });
80
+ const command = params.command;
81
+ if (!allowedQueryCommands.has(command)) {
82
+ const error = new Error("Query command not allowed");
83
+ error.status = 400;
84
+ throw error;
85
+ }
86
+ const payload = body && typeof body === "object" ? body : {};
87
+ const method = service?.[command];
88
+ if (typeof method !== "function") {
89
+ const error = new Error("Query command not implemented");
90
+ error.status = 400;
91
+ throw error;
92
+ }
93
+ return method({ query: payload });
49
94
  },
50
95
  },
51
96
  {
52
97
  method: ["dbs", "collections"], // default method is then GET or OPTIONS (set further)
53
- path: "/methods/:methodName/:params?",
98
+ path: "/methods/:methodName/:params",
99
+ validation: {
100
+ paramsSchema: z.object({ methodName: z.string().min(1), params: z.string().optional() }),
101
+ },
54
102
  handler: async (service, params) => { },
55
103
  },
56
104
  ];
package/dist/index.d.ts CHANGED
@@ -6,9 +6,19 @@ export * from './client/IdaeApiClientCollection.js';
6
6
  export * from './client/IdaeApiClient.js';
7
7
  export * from './@types/types.js';
8
8
  export * from './server/services/AuthService.js';
9
+ export * from './server/middleware/validationMiddleware.js';
10
+ export * from './server/middleware/validationLayer.js';
11
+ export * from './server/middleware/tenantContextMiddleware.js';
12
+ export * from './server/middleware/openApiMiddleware.js';
13
+ export * from './server/middleware/mcpMiddleware.js';
14
+ export * from './server/middleware/healthMiddleware.js';
15
+ export * from './server/middleware/docsMiddleware.js';
9
16
  export * from './server/middleware/databaseMiddleware.js';
17
+ export * from './server/middleware/authorizationMiddleware.js';
10
18
  export * from './server/middleware/authMiddleware.js';
11
19
  export * from './server/engine/types.js';
12
20
  export * from './server/engine/routeManager.js';
13
21
  export * from './server/engine/requestDatabaseManager.js';
14
22
  export * from './server/engine/mongooseConnectionManager.js';
23
+ export * from './__tests__/helpers/testUtils.js';
24
+ export * from './__tests__/fixtures/mockData.js';
package/dist/index.js CHANGED
@@ -7,9 +7,19 @@ export * from './client/IdaeApiClientCollection.js';
7
7
  export * from './client/IdaeApiClient.js';
8
8
  export * from './@types/types.js';
9
9
  export * from './server/services/AuthService.js';
10
+ export * from './server/middleware/validationMiddleware.js';
11
+ export * from './server/middleware/validationLayer.js';
12
+ export * from './server/middleware/tenantContextMiddleware.js';
13
+ export * from './server/middleware/openApiMiddleware.js';
14
+ export * from './server/middleware/mcpMiddleware.js';
15
+ export * from './server/middleware/healthMiddleware.js';
16
+ export * from './server/middleware/docsMiddleware.js';
10
17
  export * from './server/middleware/databaseMiddleware.js';
18
+ export * from './server/middleware/authorizationMiddleware.js';
11
19
  export * from './server/middleware/authMiddleware.js';
12
20
  export * from './server/engine/types.js';
13
21
  export * from './server/engine/routeManager.js';
14
22
  export * from './server/engine/requestDatabaseManager.js';
15
23
  export * from './server/engine/mongooseConnectionManager.js';
24
+ export * from './__tests__/helpers/testUtils.js';
25
+ export * from './__tests__/fixtures/mockData.js';
@@ -0,0 +1,12 @@
1
+ <!-- Minimal Redoc HTML, loads /openapi.json -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Redoc</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
8
+ </head>
9
+ <body>
10
+ <redoc spec-url="/openapi.json"></redoc>
11
+ </body>
12
+ </html>
@@ -0,0 +1,23 @@
1
+ <!-- Minimal Swagger UI HTML, loads /openapi.json -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Swagger UI</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css">
8
+ </head>
9
+ <body>
10
+ <div id="swagger-ui"></div>
11
+ <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
12
+ <script>
13
+ window.onload = function() {
14
+ window.ui = SwaggerUIBundle({
15
+ url: '/openapi.json',
16
+ dom_id: '#swagger-ui',
17
+ presets: [SwaggerUIBundle.presets.apis],
18
+ layout: "BaseLayout"
19
+ });
20
+ };
21
+ </script>
22
+ </body>
23
+ </html>
@@ -1,7 +1,16 @@
1
+ import '../../app.js';
1
2
  import { type IdaeDbOptions } from "@medyll/idae-db";
2
3
  import express from "express";
4
+ import { type CorsOptions } from "cors";
3
5
  import { type RouteDefinition } from "../config/routeDefinitions.js";
4
6
  import { RouteManager } from "./engine/routeManager.js";
7
+ type RateLimitOptions = {
8
+ windowMs?: number;
9
+ max?: number;
10
+ standardHeaders?: boolean | "draft-7" | "draft-6" | "draft-8";
11
+ legacyHeaders?: boolean;
12
+ [key: string]: any;
13
+ };
5
14
  interface IdaeApiOptions {
6
15
  port?: number;
7
16
  routes?: RouteDefinition[];
@@ -10,6 +19,12 @@ interface IdaeApiOptions {
10
19
  jwtSecret?: string;
11
20
  tokenExpiration?: string;
12
21
  idaeDbOptions?: IdaeDbOptions;
22
+ useMemoryDb?: boolean;
23
+ cors?: boolean | CorsOptions;
24
+ rateLimit?: false | RateLimitOptions;
25
+ payloadLimit?: string;
26
+ enableCompression?: boolean;
27
+ trustProxy?: boolean | number | string;
13
28
  }
14
29
  declare class IdaeApi {
15
30
  #private;
@@ -9,15 +9,32 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _a, _IdaeApi_instance, _IdaeApi_app, _IdaeApi_idaeApiOptions, _IdaeApi_routeManager, _IdaeApi_serverInstance, _IdaeApi_state, _IdaeApi_authMiddleware;
12
+ var _a, _IdaeApi_instance, _IdaeApi_app, _IdaeApi_idaeApiOptions, _IdaeApi_routeManager, _IdaeApi_serverInstance, _IdaeApi_state, _IdaeApi_authMiddleware, _IdaeApi_configured;
13
+ // Force l'import des types globaux (Express.Request.user)
14
+ import '../../app.js';
15
+ import { createValidationMiddleware } from "./middleware/validationMiddleware.js";
16
+ import { openApiJsonHandler } from "./middleware/openApiMiddleware.js";
17
+ import { swaggerUiHandler, redocHandler } from "./middleware/docsMiddleware.js";
18
+ import { tenantContextMiddleware } from "./middleware/tenantContextMiddleware.js";
13
19
  // packages\idae-api\src\lib\server\IdaeApi.ts
14
20
  import {} from "@medyll/idae-db";
15
21
  import express, {} from "express";
22
+ import compression from "compression";
23
+ import cors, {} from "cors";
16
24
  import { idaeDbMiddleware } from "./middleware/databaseMiddleware.js";
17
25
  import { routes as defaultRoutes, } from "../config/routeDefinitions.js";
18
26
  import { AuthMiddleWare } from "./middleware/authMiddleware.js";
27
+ import helmet from "helmet";
19
28
  import { RouteManager } from "./engine/routeManager.js";
29
+ import rateLimit from "express-rate-limit";
20
30
  import qs from "qs";
31
+ import { healthHandler, readinessHandler } from "./middleware/healthMiddleware.js";
32
+ class HttpError extends Error {
33
+ constructor(status, message) {
34
+ super(message);
35
+ this.status = status;
36
+ }
37
+ }
21
38
  class IdaeApi {
22
39
  constructor() {
23
40
  _IdaeApi_app.set(this, void 0);
@@ -26,6 +43,7 @@ class IdaeApi {
26
43
  _IdaeApi_serverInstance.set(this, void 0);
27
44
  _IdaeApi_state.set(this, "stopped");
28
45
  _IdaeApi_authMiddleware.set(this, null);
46
+ _IdaeApi_configured.set(this, false);
29
47
  __classPrivateFieldSet(this, _IdaeApi_app, express(), "f");
30
48
  __classPrivateFieldSet(this, _IdaeApi_idaeApiOptions, {}, "f");
31
49
  __classPrivateFieldSet(this, _IdaeApi_routeManager, RouteManager.getInstance(), "f");
@@ -37,8 +55,6 @@ class IdaeApi {
37
55
  parameterLimit: 1000,
38
56
  });
39
57
  });
40
- this.initializeAuth();
41
- this.configureIdaeApi();
42
58
  }
43
59
  static getInstance() {
44
60
  if (!__classPrivateFieldGet(_a, _a, "f", _IdaeApi_instance)) {
@@ -53,7 +69,14 @@ class IdaeApi {
53
69
  return __classPrivateFieldGet(this, _IdaeApi_app, "f");
54
70
  }
55
71
  setOptions(options) {
72
+ if (__classPrivateFieldGet(this, _IdaeApi_state, "f") === "running") {
73
+ throw new Error("Cannot change options while server is running");
74
+ }
56
75
  __classPrivateFieldSet(this, _IdaeApi_idaeApiOptions, { ...__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f"), ...options }, "f");
76
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").locals.idaeDbOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").idaeDbOptions;
77
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").locals.useMemoryDb = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").useMemoryDb;
78
+ this.initializeAuth();
79
+ __classPrivateFieldSet(this, _IdaeApi_configured, false, "f");
57
80
  }
58
81
  initializeAuth() {
59
82
  if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").enableAuth &&
@@ -66,30 +89,71 @@ class IdaeApi {
66
89
  this.configureMiddleware();
67
90
  this.configureRoutes();
68
91
  this.configureErrorHandling();
92
+ __classPrivateFieldSet(this, _IdaeApi_configured, true, "f");
69
93
  }
70
94
  configureMiddleware() {
71
- __classPrivateFieldGet(this, _IdaeApi_app, "f").use("/:collectionName", idaeDbMiddleware);
72
- __classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.json());
73
- __classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.urlencoded({ extended: true }));
74
- if (__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
75
- __classPrivateFieldGet(this, _IdaeApi_app, "f").use(__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").createMiddleware());
95
+ const jsonLimit = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").payloadLimit ?? "1mb";
96
+ const urlEncodedLimit = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").payloadLimit ?? "1mb";
97
+ if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").trustProxy) {
98
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").set("trust proxy", __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").trustProxy);
99
+ }
100
+ if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").enableCompression !== false) {
101
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(compression());
76
102
  }
103
+ if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors !== false) {
104
+ const corsOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors === true || __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors === undefined
105
+ ? {}
106
+ : __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors;
107
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(cors(corsOptions));
108
+ }
109
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(helmet());
110
+ if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").rateLimit !== false) {
111
+ const limiterOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").rateLimit ?? {
112
+ windowMs: 60000,
113
+ max: 100,
114
+ standardHeaders: "draft-7",
115
+ legacyHeaders: false,
116
+ };
117
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(rateLimit(limiterOptions));
118
+ }
119
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.json({ limit: jsonLimit }));
120
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.urlencoded({ extended: true, limit: urlEncodedLimit }));
121
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").use("/:collectionName", idaeDbMiddleware);
122
+ // Do not inject tenant context middleware globally; it will be added per-route for protected routes
77
123
  }
78
124
  configureRoutes() {
125
+ // Health and readiness endpoints (always unprotected)
126
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").get("/health", healthHandler);
127
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").get("/ready", readinessHandler);
128
+ // OpenAPI and docs endpoints (always unprotected)
129
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").get("/openapi.json", openApiJsonHandler);
130
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").get("/docs", swaggerUiHandler);
131
+ __classPrivateFieldGet(this, _IdaeApi_app, "f").get("/redoc", redocHandler);
132
+ if (__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
133
+ __classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").configureAuthRoutes(__classPrivateFieldGet(this, _IdaeApi_app, "f"));
134
+ }
79
135
  __classPrivateFieldGet(this, _IdaeApi_routeManager, "f").addRoutes(defaultRoutes);
80
136
  if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").routes) {
81
137
  __classPrivateFieldGet(this, _IdaeApi_routeManager, "f").addRoutes(__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").routes);
82
138
  }
83
139
  __classPrivateFieldGet(this, _IdaeApi_routeManager, "f").getRoutes().forEach(this.addRouteToExpress.bind(this));
84
- if (__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
85
- __classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").configureAuthRoutes(__classPrivateFieldGet(this, _IdaeApi_app, "f"));
86
- }
87
140
  }
88
141
  // Add a route to Express
89
142
  addRouteToExpress(route) {
90
143
  const handlers = [];
144
+ // Add validation middleware if present
145
+ if (route.validation) {
146
+ handlers.push(createValidationMiddleware(route.validation));
147
+ }
148
+ // Add auth and tenant context middleware for protected routes
91
149
  if (route.requiresAuth && __classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
92
150
  handlers.push(__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").createMiddleware());
151
+ handlers.push(tenantContextMiddleware({ required: true }));
152
+ }
153
+ // Add RBAC/ABAC authorization middleware if specified
154
+ if (route.authorization) {
155
+ const { authorize } = require("./middleware/authorizationMiddleware.js");
156
+ handlers.push(authorize(route.authorization));
93
157
  }
94
158
  handlers.push(this.handleRequest(route.handler));
95
159
  if (Array.isArray(route.method)) {
@@ -101,8 +165,15 @@ class IdaeApi {
101
165
  }
102
166
  configureErrorHandling() {
103
167
  __classPrivateFieldGet(this, _IdaeApi_app, "f").use((err, req, res, next) => {
104
- console.error(err.stack);
105
- res.status(500).json({ error: err.message });
168
+ const status = err?.status && Number.isInteger(err.status)
169
+ ? err.status
170
+ : 500;
171
+ const isServerError = status >= 500;
172
+ const message = isServerError ? "Internal Server Error" : err.message;
173
+ if (isServerError) {
174
+ console.error(err.stack ?? err.message);
175
+ }
176
+ res.status(status).json({ error: message });
106
177
  });
107
178
  }
108
179
  start() {
@@ -110,7 +181,11 @@ class IdaeApi {
110
181
  console.log("Server is already running.");
111
182
  return;
112
183
  }
113
- const port = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").port || 3000;
184
+ if (!__classPrivateFieldGet(this, _IdaeApi_configured, "f")) {
185
+ this.initializeAuth();
186
+ this.configureIdaeApi();
187
+ }
188
+ const port = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").port ?? 3000;
114
189
  __classPrivateFieldSet(this, _IdaeApi_serverInstance, __classPrivateFieldGet(this, _IdaeApi_app, "f").listen(port, () => {
115
190
  console.log(`Server is running on port: ${port}`);
116
191
  __classPrivateFieldSet(this, _IdaeApi_state, "running", "f");
@@ -157,18 +232,15 @@ class IdaeApi {
157
232
  try {
158
233
  const connectedCollection = req.connectedCollection;
159
234
  if (!connectedCollection) {
160
- throw new Error("Database connection not established");
235
+ throw new HttpError(500, "Database connection not established");
161
236
  }
162
- console.log("----------------------------------------------");
163
- console.log("body", req.body);
164
- console.log("params", req.params);
165
- console.log("query", req.query);
166
- console.log("dbName", req.dbName);
167
- // const result = await action(databaseService, req.params, req.body);
168
237
  const result = await action(connectedCollection, req.params, req.body, req.query.params ?? req.query);
169
238
  res.json(result);
170
239
  }
171
240
  catch (error) {
241
+ if (!error?.status) {
242
+ error.status = 500;
243
+ }
172
244
  next(error);
173
245
  }
174
246
  };
@@ -178,7 +250,7 @@ class IdaeApi {
178
250
  return __classPrivateFieldGet(this, _IdaeApi_routeManager, "f");
179
251
  }
180
252
  }
181
- _a = IdaeApi, _IdaeApi_app = new WeakMap(), _IdaeApi_idaeApiOptions = new WeakMap(), _IdaeApi_routeManager = new WeakMap(), _IdaeApi_serverInstance = new WeakMap(), _IdaeApi_state = new WeakMap(), _IdaeApi_authMiddleware = new WeakMap();
253
+ _a = IdaeApi, _IdaeApi_app = new WeakMap(), _IdaeApi_idaeApiOptions = new WeakMap(), _IdaeApi_routeManager = new WeakMap(), _IdaeApi_serverInstance = new WeakMap(), _IdaeApi_state = new WeakMap(), _IdaeApi_authMiddleware = new WeakMap(), _IdaeApi_configured = new WeakMap();
182
254
  _IdaeApi_instance = { value: null };
183
255
  // Export a single instance of ApiServer
184
256
  const idaeApi = IdaeApi.getInstance();
@@ -19,4 +19,4 @@ declare class RequestDatabaseManager {
19
19
  }
20
20
  declare const requestDatabaseManager: RequestDatabaseManager;
21
21
  export default requestDatabaseManager;
22
- export { RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
22
+ export { RequestDatabaseManager, RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
@@ -18,23 +18,19 @@ class RequestDatabaseManager {
18
18
  return RequestDatabaseManager.instance;
19
19
  }
20
20
  fromReq(req) {
21
- const collectionName = req.params.collectionName || "default";
22
- const dbName = getDbNameFromCollectionName(collectionName, this.config.defaultDbName);
23
- const collectionNames = collectionName.split(".")?.[1] ?? collectionName.split(".")?.[0];
21
+ const rawCollectionName = req.params?.collectionName ?? "default";
22
+ const [dbPart, ...rest] = rawCollectionName.split(".");
23
+ const dbName = rest.length > 0 ? dbPart || this.config.defaultDbName : this.config.defaultDbName;
24
+ const collectionName = rest.length > 0 ? rest.join(".") || "default" : rawCollectionName || "default";
24
25
  const dbUri = `${this.config.connectionPrefix}${this.config.host}:${this.config.port}/${dbName}`;
25
26
  return {
26
27
  dbName,
27
- collectionName: collectionNames,
28
+ collectionName,
28
29
  dbUri,
29
30
  };
30
- function getDbNameFromCollectionName(collectionName, defaultDbName) {
31
- return collectionName.includes(".")
32
- ? collectionName.split(".")[0]
33
- : defaultDbName;
34
- }
35
31
  }
36
32
  async closeAllConnections() { }
37
33
  }
38
34
  const requestDatabaseManager = RequestDatabaseManager.getInstance();
39
35
  export default requestDatabaseManager;
40
- export { RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
36
+ export { RequestDatabaseManager, RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
@@ -9,4 +9,5 @@ export declare class RouteManager {
9
9
  getRoutes(): RouteDefinition[];
10
10
  enableRoute(path: string, method: string | string[]): void;
11
11
  disableRoute(path: string, method: string | string[]): void;
12
+ clearRoutes(): void;
12
13
  }
@@ -3,12 +3,22 @@ export class RouteManager {
3
3
  this.routes = [];
4
4
  }
5
5
  static getInstance() {
6
- if (!RouteManager.instance) {
7
- RouteManager.instance = new RouteManager();
6
+ const instance = RouteManager.instance ?? new RouteManager();
7
+ // In test runs we want isolated state between cases
8
+ if (process.env.NODE_ENV === "test") {
9
+ instance.clearRoutes();
8
10
  }
9
- return RouteManager.instance;
11
+ RouteManager.instance = instance;
12
+ return instance;
10
13
  }
11
14
  addRoute(route) {
15
+ // Remove any existing route with the same path/method to avoid stale handlers
16
+ this.routes = this.routes.filter((r) => r.path !== route.path ||
17
+ (Array.isArray(r.method)
18
+ ? !Array.isArray(route.method) || !route.method.some((m) => r.method.includes(m))
19
+ : Array.isArray(route.method)
20
+ ? !route.method.includes(r.method)
21
+ : r.method !== route.method));
12
22
  this.routes.push({ ...route, disabled: route.disabled || false });
13
23
  }
14
24
  addRoutes(routes) {
@@ -18,21 +28,27 @@ export class RouteManager {
18
28
  return this.routes.filter((route) => !route.disabled);
19
29
  }
20
30
  enableRoute(path, method) {
21
- const route = this.routes.find((r) => r.path === path &&
22
- (Array.isArray(r.method)
23
- ? r.method.includes(method)
24
- : r.method === method));
25
- if (route) {
26
- route.disabled = false;
27
- }
31
+ this.routes.forEach((r) => {
32
+ if (r.path === path &&
33
+ (Array.isArray(r.method)
34
+ ? r.method.includes(method)
35
+ : r.method === method)) {
36
+ r.disabled = false;
37
+ }
38
+ });
28
39
  }
29
40
  disableRoute(path, method) {
30
- const route = this.routes.find((r) => r.path === path &&
31
- (Array.isArray(r.method)
32
- ? r.method.includes(method)
33
- : r.method === method));
34
- if (route) {
35
- route.disabled = true;
36
- }
41
+ this.routes.forEach((r) => {
42
+ if (r.path === path &&
43
+ (Array.isArray(r.method)
44
+ ? r.method.includes(method)
45
+ : r.method === method)) {
46
+ r.disabled = true;
47
+ }
48
+ });
49
+ }
50
+ // Utility primarily for tests to reset state safely
51
+ clearRoutes() {
52
+ this.routes = [];
37
53
  }
38
54
  }
@@ -0,0 +1,46 @@
1
+ # Middleware Documentation
2
+
3
+ This document describes all middleware available in the `@medyll/idae-api` package, their purpose, usage, and integration order.
4
+
5
+ ## Overview
6
+
7
+ Middleware in this project is located in `src/lib/server/middleware/`. Each middleware serves a specific purpose (validation, authentication, multi-tenancy, docs, health, etc.) and is applied in a specific order in the Express app lifecycle.
8
+
9
+ ## List of Middleware
10
+
11
+ - **authMiddleware**: Handles JWT authentication and attaches user context to requests.
12
+ - **authorizationMiddleware**: Enforces RBAC/ABAC policies per route.
13
+ - **databaseMiddleware**: Injects `req.idaeDb` and manages per-request DB/collection context.
14
+ - **tenantContextMiddleware**: Extracts tenant context from JWT/user and enforces multi-tenancy.
15
+ - **validationMiddleware**: Validates request bodies/queries using Zod schemas.
16
+ - **validationLayer**: (Advanced) Layered validation, supports Zod and future OpenAPI/ajv.
17
+ - **docsMiddleware**: Serves Swagger UI and Redoc API docs.
18
+ - **openApiMiddleware**: Serves OpenAPI JSON spec.
19
+ - **healthMiddleware**: Provides health and readiness endpoints.
20
+ - **mcpMiddleware**: Placeholder for MCP-specific logic (future extension).
21
+
22
+ ## Middleware Application Order
23
+
24
+ The recommended order (see `IdaeApi.ts`):
25
+ 1. Express body/URL parsers
26
+ 2. `authMiddleware` (if enabled)
27
+ 3. `tenantContextMiddleware` (if multi-tenancy enabled)
28
+ 4. `databaseMiddleware`
29
+ 5. `validationMiddleware`/`validationLayer`
30
+ 6. Custom routes
31
+ 7. `docsMiddleware`, `openApiMiddleware`, `healthMiddleware`
32
+ 8. Error handler
33
+
34
+ ## Usage Example
35
+
36
+ ```
37
+ import { authMiddleware, tenantContextMiddleware, databaseMiddleware } from './middleware';
38
+ app.use(authMiddleware(...));
39
+ app.use(tenantContextMiddleware(...));
40
+ app.use(databaseMiddleware);
41
+ ```
42
+
43
+ ## Notes
44
+ - Always apply `authMiddleware` before `databaseMiddleware` if using per-user DB context.
45
+ - `tenantContextMiddleware` is required for strict multi-tenancy.
46
+ - See each middleware file for detailed JSDoc and options.