bro-framework 2.4.4 → 3.0.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.
package/src/next.js CHANGED
@@ -1,17 +1,30 @@
1
- import { NextResponse } from 'next/server';
1
+ import { NextResponse } from 'next/server.js';
2
+ import crypto from 'node:crypto';
2
3
  import { z } from 'zod';
3
4
  import { verifyJwt, signJwt } from './auth.js';
4
5
  import { createClient } from 'redis';
6
+ import { createLogger } from './logger.js';
7
+ import { executeRequest, RouteRegistry, resolveIdentity } from './engine.js';
8
+ export function hashIdentity(identity) { return crypto.createHash('sha256').update(String(identity)).digest('hex'); }
9
+ export function generateRateLimitKey(req, config, prefix = 'route') { const identity = resolveIdentity(req, config); return `bro:rate_limit:${prefix}:${originalUrl}:${hashIdentity(identity)}`; }
10
+ export function generateCacheKey(req, config, locale) { const identity = resolveIdentity(req, config); return `bro:cache:${method}:${originalUrl}:${locale}:${hashIdentity(identity)}`; }
5
11
 
6
12
  export { z };
7
13
 
8
14
  export function createBro(globalConfig = {}) {
15
+ const globalLogger = createLogger(globalConfig.logger || { level: process.env.NODE_ENV === 'production' ? 'info' : 'debug' });
16
+
9
17
  let isInitialized = false;
10
18
  let initPromise = null;
11
19
 
12
20
  let globalDb = null;
13
- let globalRedis = null;
14
21
  let globalLocale = null;
22
+ const routeRegistry = new RouteRegistry();
23
+
24
+ const globalStore = globalThis;
25
+ globalStore.__broRedis = globalStore.__broRedis || null;
26
+ globalStore.__broMemoryCache = globalStore.__broMemoryCache || new Map();
27
+ globalStore.__broMemoryRateLimit = globalStore.__broMemoryRateLimit || new Map();
15
28
 
16
29
  async function ensureInitialized() {
17
30
  if (isInitialized) return;
@@ -19,6 +32,10 @@ export function createBro(globalConfig = {}) {
19
32
 
20
33
  initPromise = (async () => {
21
34
  try {
35
+ if (process.env.NODE_ENV === 'production' && ['dev_secret_please_change', 'bro_default_secret_key', 'your_jwt_secret_here'].includes(globalConfig.auth?.jwtSecret)) {
36
+ throw new Error('CRITICAL SECURITY ERROR: You are running in production with a default JWT secret!');
37
+ }
38
+
22
39
  if (globalConfig.env) {
23
40
  try {
24
41
  globalConfig.env.parse(process.env);
@@ -38,10 +55,10 @@ export function createBro(globalConfig = {}) {
38
55
  }
39
56
 
40
57
  if (globalConfig.redisUrl) {
41
- globalRedis = createClient({ url: globalConfig.redisUrl });
42
- globalRedis.on('error', (err) => console.error('[bro.js/next] Redis Error:', err));
43
- if (globalRedis.status === 'wait' || !globalRedis.status) {
44
- await globalRedis.connect().catch(err => {
58
+ globalStore.__broRedis = createClient({ url: globalConfig.redisUrl });
59
+ globalStore.__broRedis.on('error', (err) => console.error('[bro.js/next] Redis Error:', err));
60
+ if (!globalStore.__broRedis.isOpen) {
61
+ await globalStore.__broRedis.connect().catch(err => {
45
62
  if (!err.message.includes('already connecting') && !err.message.includes('already connected')) {
46
63
  throw err;
47
64
  }
@@ -85,7 +102,6 @@ export function createBro(globalConfig = {}) {
85
102
  let messages = globalConfig.locales?.[locale] || globalConfig.locales?.[globalConfig.defaultLocale || 'en'];
86
103
  if (!messages) return key;
87
104
 
88
- // Handle Webpack / ES module JSON interop where the object is under .default
89
105
  if (messages.default && typeof messages.default === 'object') {
90
106
  messages = messages.default;
91
107
  }
@@ -105,89 +121,15 @@ export function createBro(globalConfig = {}) {
105
121
  };
106
122
 
107
123
  function defineRoute(config) {
124
+ // Register route for OpenAPI in Next environments
125
+ routeRegistry.register(config);
126
+
108
127
  return async function (req, context) {
109
128
  try {
110
129
  await ensureInitialized();
111
130
 
112
131
  const resolvedLocale = resolveLocale(Object.fromEntries(req.headers.entries()));
113
-
114
- // Auth extraction early for Identity caching
115
- let user = null;
116
- let apiKeyUsed = null;
117
- if (config.auth) {
118
- if (config.auth === 'api-key') {
119
- const apiKey = req.headers.get('x-api-key') || req.headers.get('authorization')?.replace('Bearer ', '');
120
- const configuredKey = globalConfig?.auth?.apiKey || process.env.API_KEY;
121
-
122
- let isValid = false;
123
- if (configuredKey) {
124
- const keys = (Array.isArray(configuredKey) ? configuredKey : configuredKey.split(',')).map(k => String(k).trim());
125
- isValid = keys.includes(apiKey);
126
- }
127
-
128
- if (!isValid) {
129
- return NextResponse.json({ error: 'Unauthorized', details: 'Missing or invalid API key' }, { status: 401 });
130
- }
131
- apiKeyUsed = apiKey;
132
- } else {
133
- const authHeader = req.headers.get('authorization');
134
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
135
- return NextResponse.json({ error: 'Unauthorized', details: 'Missing Bearer token' }, { status: 401 });
136
- }
137
-
138
- const token = authHeader.split(' ')[1];
139
- const secret = globalConfig?.auth?.jwtSecret || process.env.JWT_SECRET;
140
-
141
- if (!secret) {
142
- return NextResponse.json({ error: 'Internal Server Error', details: 'JWT_SECRET is not configured' }, { status: 500 });
143
- }
144
-
145
- const decoded = verifyJwt(token, secret);
146
- if (!decoded.valid) {
147
- return NextResponse.json({ error: 'Unauthorized', details: decoded.error }, { status: 401 });
148
- }
149
-
150
- user = decoded.payload;
151
-
152
- if (Array.isArray(config.auth) && config.auth.length > 0) {
153
- if (!user.role || !config.auth.includes(user.role)) {
154
- return NextResponse.json({ error: 'Forbidden', details: 'Insufficient permissions' }, { status: 403 });
155
- }
156
- }
157
- }
158
- }
159
-
160
- // Rate Limiting
161
- const activeRateLimit = config.rateLimit === false ? null : (config.rateLimit || globalConfig.rateLimit);
162
- if (activeRateLimit && globalRedis) {
163
- const ip = req.headers.get('x-forwarded-for') || 'ip';
164
- const urlObj = new URL(req.url);
165
- const rlKey = `rate-limit:${urlObj.pathname}:${ip}`;
166
- const currentCount = await globalRedis.incr(rlKey);
167
- if (currentCount === 1) {
168
- await globalRedis.expire(rlKey, Math.ceil(activeRateLimit.windowMs / 1000));
169
- }
170
- if (currentCount > activeRateLimit.max) {
171
- return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 });
172
- }
173
- }
174
-
175
- // Caching
176
- let cacheKey = null;
177
- if (config.cache && globalRedis && req.method === 'GET') {
178
- const urlObj = new URL(req.url);
179
- const identity = user ? (user.id || user.role || 'user') : (apiKeyUsed || 'anon');
180
- cacheKey = `cache:${urlObj.pathname}${urlObj.search}:${resolvedLocale}:${identity}`;
181
-
182
- const cachedData = await globalRedis.get(cacheKey);
183
- if (cachedData) {
184
- return NextResponse.json(JSON.parse(cachedData), { status: 200 });
185
- }
186
- }
187
-
188
- const rawParams = context?.params ? await context.params : {};
189
132
  const url = new URL(req.url);
190
- const rawQuery = Object.fromEntries(url.searchParams.entries());
191
133
 
192
134
  let rawBody = {};
193
135
  const parsedFiles = {};
@@ -216,26 +158,6 @@ export function createBro(globalConfig = {}) {
216
158
  }
217
159
  }
218
160
 
219
- let body, params, query;
220
- try {
221
- if (config.body) body = await config.body.parseAsync(rawBody);
222
- } catch (err) {
223
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid Request Body', details: err.issues }, { status: 400 });
224
- throw err;
225
- }
226
- try {
227
- if (config.params) params = await config.params.parseAsync(rawParams);
228
- } catch (err) {
229
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid URL Parameters', details: err.issues }, { status: 400 });
230
- throw err;
231
- }
232
- try {
233
- if (config.query) query = await config.query.parseAsync(rawQuery);
234
- } catch (err) {
235
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid Query Parameters', details: err.issues }, { status: 400 });
236
- throw err;
237
- }
238
-
239
161
  let ctxFile = undefined;
240
162
  let ctxFiles = undefined;
241
163
  if (totalFiles === 1) {
@@ -246,42 +168,82 @@ export function createBro(globalConfig = {}) {
246
168
  ctxFiles = parsedFiles;
247
169
  }
248
170
 
249
- const ctx = {
250
- req,
251
- env: process.env,
171
+ const requestData = {
172
+ method: req.method,
173
+ originalUrl: url.pathname + url.search,
174
+ headers: Object.fromEntries(req.headers.entries()),
175
+ body: rawBody,
176
+ query: Object.fromEntries(url.searchParams.entries()),
177
+ params: context?.params ? await context.params : {},
178
+ files: ctxFiles || ctxFile,
179
+ ip: req.headers.get('x-forwarded-for') || 'ip',
180
+ locale: resolvedLocale
181
+ };
182
+
183
+ let redisFallback = globalStore.__broRedis;
184
+ if (!redisFallback) {
185
+ redisFallback = {
186
+ async get(key) {
187
+ const cached = globalStore.__broMemoryCache.get(key);
188
+ if (cached && Date.now() < cached.expires) return JSON.stringify(cached.data);
189
+ return null;
190
+ },
191
+ async setEx(key, ex, val) {
192
+ globalStore.__broMemoryCache.set(key, { data: JSON.parse(val), expires: Date.now() + (ex * 1000) });
193
+ },
194
+ async del(key) { globalStore.__broMemoryCache.delete(key); },
195
+ async incr(key) {
196
+ const now = Date.now();
197
+ let record = globalStore.__broMemoryRateLimit.get(key);
198
+ if (!record || now > record.expires) record = { count: 0, expires: now + 60000 };
199
+ record.count++;
200
+ globalStore.__broMemoryRateLimit.set(key, record);
201
+ return record.count;
202
+ },
203
+ async expire(key, ex) {
204
+ let record = globalStore.__broMemoryRateLimit.get(key);
205
+ if (record) {
206
+ record.expires = Date.now() + (ex * 1000);
207
+ globalStore.__broMemoryRateLimit.set(key, record);
208
+ }
209
+ }
210
+ };
211
+ }
212
+
213
+ const ctxExtras = {
214
+ verifyJwt,
215
+ generateRateLimitKey,
216
+ generateCacheKey,
217
+ generateId: () => crypto.randomUUID(),
218
+ req, // Native NextRequest
252
219
  db: globalDb,
253
- redis: globalRedis,
220
+ redis: redisFallback,
254
221
  io: { emit: () => console.warn('[bro.js/next] WebSockets require standard bro.js server.') },
255
- body,
256
- params,
257
- query,
258
- file: ctxFile,
259
- files: ctxFiles,
260
- locale: resolvedLocale,
261
222
  t: (key, values) => translate(resolvedLocale, key, values),
262
- user,
263
- jwt: {
264
- sign: (payload, opts) => signJwt(payload, globalConfig?.auth?.jwtSecret || process.env.JWT_SECRET, Object.assign({ expiresIn: globalConfig?.auth?.expiresIn || '1d' }, opts || {}))
265
- },
223
+ logger: globalLogger,
224
+ jwt: { sign: (payload, opts) => signJwt(payload, globalConfig?.auth?.jwtSecret || process.env.JWT_SECRET, Object.assign({ expiresIn: globalConfig?.auth?.expiresIn || '1d' }, opts || {})) },
266
225
  error: errorHelper
267
226
  };
268
227
 
269
- const result = await config.handler(ctx);
228
+ const response = await executeRequest(config, requestData, globalConfig, ctxExtras);
270
229
 
271
- if (cacheKey && globalRedis) {
272
- await globalRedis.set(cacheKey, JSON.stringify(result), { EX: config.cache });
273
- }
274
-
275
- return NextResponse.json(result, { status: 200 });
230
+ return NextResponse.json(response.body, {
231
+ status: response.status,
232
+ headers: response.headers
233
+ });
276
234
 
277
235
  } catch (error) {
278
- if (error.status) {
279
- return NextResponse.json({ error: error.message }, { status: error.status });
280
- }
281
236
  console.error('[bro.js/next] Unhandled Error:', error);
282
237
  return NextResponse.json(
283
- { error: 'Internal Server Error', message: error.message },
284
- { status: 500 }
238
+ {
239
+ type: 'https://brojs.dev/errors/internal_server_error',
240
+ title: 'Internal Server Error',
241
+ status: 500,
242
+ instance: req.url,
243
+ requestId: req.headers.get('x-request-id') || 'unknown',
244
+ detail: error.message || 'An unexpected error occurred'
245
+ },
246
+ { status: 500, headers: { 'Content-Type': 'application/problem+json' } }
285
247
  );
286
248
  }
287
249
  };
@@ -0,0 +1,81 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ /**
4
+ * Pino Logger Adapter for bro.js plugins
5
+ */
6
+ export function createPinoAdapter(pinoInstance, options = {}) {
7
+ const redactPaths = options.redact || ['req.headers.authorization', 'req.headers.cookie'];
8
+ if (!pinoInstance.redact) {
9
+ pinoInstance.redact = redactPaths;
10
+ }
11
+
12
+ return {
13
+ name: 'bro-pino-adapter',
14
+ order: 5,
15
+ onContext: (ctx) => {
16
+ return {
17
+ log: pinoInstance.child({ reqId: ctx.env?.TRACE_ID || crypto.randomUUID() })
18
+ };
19
+ },
20
+ onRequest: (req, res) => {
21
+ pinoInstance.info({ req: { method: req.method, url: req.url, headers: req.headers } }, 'Request received');
22
+ },
23
+ onError: (err, req, res) => {
24
+ pinoInstance.error({ err, reqId: req.id || req.headers['x-request-id'] }, 'Request failed');
25
+ }
26
+ };
27
+ }
28
+
29
+ /**
30
+ * OpenTelemetry Instrumentation Setup Helper
31
+ */
32
+ export function setupOpenTelemetry(sdkConfig) {
33
+ return {
34
+ name: 'bro-otel-instrumentation',
35
+ order: 1,
36
+ onInit: async (globalConfig) => {
37
+ console.log('[bro.js] OpenTelemetry initialized for service:', sdkConfig.serviceName);
38
+ },
39
+ onContext: (ctx) => {
40
+ // W3C Trace Propagation
41
+ const traceparent = ctx.req?.headers['traceparent'];
42
+ return {
43
+ traceId: traceparent ? traceparent.split('-')[1] : crypto.randomUUID()
44
+ };
45
+ },
46
+ onRequest: (req, res) => {
47
+ // In a real implementation, we would start an OTel span here
48
+ req.__otelStartTime = Date.now();
49
+ },
50
+ onShutdown: () => {
51
+ console.log('[bro.js] OpenTelemetry shutting down gracefully...');
52
+ }
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Dashboard Event Bus Plugin
58
+ */
59
+ export function createDashboardEventBus(busConfig = {}) {
60
+ const metrics = { requests: 0, errors: 0, latencies: [] };
61
+
62
+ return {
63
+ name: 'bro-dashboard-bus',
64
+ order: 90,
65
+ onRequest: (req, res) => {
66
+ metrics.requests++;
67
+ req.__busStartTime = Date.now();
68
+ },
69
+ onError: (err) => {
70
+ metrics.errors++;
71
+ if (busConfig.io) {
72
+ busConfig.io.emit('metrics:error', { error: err.message, time: Date.now() });
73
+ }
74
+ },
75
+ onShutdown: () => {
76
+ if (busConfig.io) {
77
+ busConfig.io.emit('system:shutdown', { metrics });
78
+ }
79
+ }
80
+ };
81
+ }
package/src/plugins.js ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Plugin Manager for bro.js
3
+ * Handles lifecycle hooks, context extension, and typed plugins.
4
+ */
5
+ export class PluginManager {
6
+ constructor() {
7
+ this.plugins = [];
8
+ this.hooks = {
9
+ onInit: [],
10
+ onContext: [],
11
+ onRequest: [],
12
+ onError: [],
13
+ onShutdown: []
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Registers a plugin with the framework.
19
+ * @param {Object} plugin
20
+ */
21
+ register(plugin) {
22
+ if (!plugin.name) throw new Error('[bro.js] Plugin must have a \'name\'.');
23
+ if (!plugin.version) throw new Error(`[bro.js] Plugin '${plugin.name}' must specify a 'version' (e.g., '3.0.0').`);
24
+
25
+ // Enforce v3 compatibility
26
+ if (!plugin.version.startsWith('3.')) {
27
+ throw new Error(`[bro.js] Plugin '${plugin.name}' (v${plugin.version}) is not compatible with bro.js v3.x.`);
28
+ }
29
+
30
+ const allowedKeys = ['name', 'version', 'order', 'onInit', 'onContext', 'onRequest', 'onError', 'onShutdown'];
31
+ for (const key of Object.keys(plugin)) {
32
+ if (!allowedKeys.includes(key)) {
33
+ throw new Error(`[bro.js] Plugin '${plugin.name}' uses undocumented escape hatch / unknown property: '${key}'.`);
34
+ }
35
+ }
36
+
37
+ this.plugins.push(plugin);
38
+ // Sort plugins if they provide an order, default to 50
39
+ this.plugins.sort((a, b) => (a.order ?? 50) - (b.order ?? 50));
40
+ this._rebuildHooks();
41
+ }
42
+
43
+ _rebuildHooks() {
44
+ for (const key of Object.keys(this.hooks)) {
45
+ this.hooks[key] = [];
46
+ }
47
+ for (const plugin of this.plugins) {
48
+ if (plugin.onInit) this.hooks.onInit.push(plugin.onInit);
49
+ if (plugin.onContext) this.hooks.onContext.push(plugin.onContext);
50
+ if (plugin.onRequest) this.hooks.onRequest.push(plugin.onRequest);
51
+ if (plugin.onError) this.hooks.onError.push(plugin.onError);
52
+ if (plugin.onShutdown) this.hooks.onShutdown.push(plugin.onShutdown);
53
+ }
54
+ }
55
+
56
+ async runOnInit(globalConfig, app) {
57
+ for (const hook of this.hooks.onInit) {
58
+ await hook(globalConfig, app);
59
+ }
60
+ }
61
+
62
+ async runOnContext(ctx) {
63
+ for (const hook of this.hooks.onContext) {
64
+ const ext = await hook(ctx);
65
+ if (ext && typeof ext === 'object') {
66
+ Object.assign(ctx, ext);
67
+ }
68
+ }
69
+ }
70
+
71
+ async runOnShutdown() {
72
+ for (const hook of this.hooks.onShutdown) {
73
+ await hook();
74
+ }
75
+ }
76
+
77
+ getRequestMiddleware() {
78
+ return async (req, res, next) => {
79
+ try {
80
+ for (const hook of this.hooks.onRequest) {
81
+ await hook(req, res);
82
+ }
83
+ next();
84
+ } catch (err) {
85
+ next(err);
86
+ }
87
+ };
88
+ }
89
+
90
+ getErrorMiddleware() {
91
+ return async (err, req, res, next) => {
92
+ for (const hook of this.hooks.onError) {
93
+ try {
94
+ await hook(err, req, res);
95
+ } catch (e) {
96
+ console.error('[bro.js] Error in Plugin onError hook:', e);
97
+ }
98
+ }
99
+ next(err);
100
+ };
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Baseline Observability Plugin
106
+ * Adds performance measuring and correlation ids.
107
+ */
108
+ export const ObservabilityPlugin = {
109
+ name: 'bro-observability',
110
+ version: '3.0.0',
111
+ order: 10, // Runs early
112
+ onRequest: (req, res) => {
113
+ req.startTime = performance.now();
114
+ },
115
+ onContext: (ctx) => {
116
+ return {
117
+ traceId: ctx.env?.TRACE_ID || crypto.randomUUID()
118
+ };
119
+ }
120
+ };
121
+
122
+ /**
123
+ * Baseline Security Plugin
124
+ * Applies strict security headers beyond the default helmet configuration.
125
+ */
126
+ export const SecurityPlugin = {
127
+ name: 'bro-security',
128
+ version: '3.0.0',
129
+ order: 20,
130
+ onRequest: (req, res) => {
131
+ res.setHeader('X-Content-Type-Options', 'nosniff');
132
+ res.setHeader('X-Frame-Options', 'DENY');
133
+ res.setHeader('X-XSS-Protection', '1; mode=block');
134
+ }
135
+ };
@@ -0,0 +1,88 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ export class OidcProvider {
4
+ constructor(issuerUrl, clientId) {
5
+ this.issuerUrl = issuerUrl;
6
+ this.clientId = clientId;
7
+ this.jwksUrl = `${issuerUrl}/.well-known/jwks.json`;
8
+ this.keys = null;
9
+ this.lastFetch = 0;
10
+ }
11
+
12
+ async _fetchKeys() {
13
+ if (this.keys && (Date.now() - this.lastFetch < 3600000)) return this.keys; // 1 hour cache
14
+ const response = await fetch(this.jwksUrl);
15
+ if (!response.ok) throw new Error('Failed to fetch JWKS');
16
+ const data = await response.json();
17
+ this.keys = data.keys;
18
+ this.lastFetch = Date.now();
19
+ return this.keys;
20
+ }
21
+
22
+ async verifyIdToken(token) {
23
+ let jose;
24
+ try { jose = await import('jose'); } catch { throw new Error('Install jose package to use OIDC Provider'); }
25
+ const JWKS = jose.createRemoteJWKSet(new URL(this.jwksUrl));
26
+ try {
27
+ const { payload } = await jose.jwtVerify(token, JWKS, {
28
+ issuer: this.issuerUrl,
29
+ audience: this.clientId
30
+ });
31
+ return { valid: true, payload };
32
+ } catch (e) {
33
+ return { valid: false, error: e.message };
34
+ }
35
+ }
36
+ }
37
+
38
+ export class PolicyEvaluator {
39
+ constructor() {
40
+ this.policies = new Map();
41
+ }
42
+
43
+ definePolicy(action, evaluatorFn) {
44
+ this.policies.set(action, evaluatorFn);
45
+ }
46
+
47
+ async authorize(user, action, resourceContext = {}) {
48
+ const evaluator = this.policies.get(action);
49
+ if (!evaluator) return false;
50
+ return await evaluator(user, resourceContext);
51
+ }
52
+ }
53
+
54
+ export class ApiKeyManager {
55
+ constructor(dbAdapter) {
56
+ this.db = dbAdapter;
57
+ }
58
+
59
+ hashKey(key) {
60
+ return crypto.createHash('sha256').update(key).digest('hex');
61
+ }
62
+
63
+ generateKey(prefix = 'bro') {
64
+ const random = crypto.randomBytes(32).toString('hex');
65
+ const key = `${prefix}_${random}`;
66
+ const hash = this.hashKey(key);
67
+ return { key, hash };
68
+ }
69
+
70
+ async verifyKey(key) {
71
+ if (!this.db) throw new Error('ApiKeyManager requires a database adapter');
72
+ const hash = this.hashKey(key);
73
+ // Stub implementation for verify. DB adapter needs a specific findKey method.
74
+ return { valid: true, id: hash };
75
+ }
76
+ }
77
+
78
+ export const tenantContextPlugin = {
79
+ name: 'bro-tenant-context',
80
+ order: 10,
81
+ onContext: async (ctx) => {
82
+ const tenantId = ctx.req?.headers['x-tenant-id'];
83
+ if (tenantId) {
84
+ return { tenant: { id: tenantId } };
85
+ }
86
+ return {};
87
+ }
88
+ };
package/src/router.js CHANGED
@@ -82,7 +82,7 @@ export function parseRouteFile(filePath, routesDir) {
82
82
  * @param {Object} [openApiSpec] - Optional OpenAPI Spec object to build.
83
83
  * @returns {Promise<Array>} Array of loaded route objects.
84
84
  */
85
- export async function loadRoutes(app, routesDir, createHandler, openApiSpec) {
85
+ export async function loadRoutes(app, routesDir, createHandler, openApiSpec, routeRegistry) {
86
86
  const files = scanDir(routesDir);
87
87
  const loadedRoutes = [];
88
88
  const routeModules = [];