bro-framework 2.4.5 → 3.0.1

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,16 +1,25 @@
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
21
  let globalLocale = null;
22
+ const routeRegistry = new RouteRegistry();
14
23
 
15
24
  const globalStore = globalThis;
16
25
  globalStore.__broRedis = globalStore.__broRedis || null;
@@ -23,6 +32,10 @@ export function createBro(globalConfig = {}) {
23
32
 
24
33
  initPromise = (async () => {
25
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
+
26
39
  if (globalConfig.env) {
27
40
  try {
28
41
  globalConfig.env.parse(process.env);
@@ -89,7 +102,6 @@ export function createBro(globalConfig = {}) {
89
102
  let messages = globalConfig.locales?.[locale] || globalConfig.locales?.[globalConfig.defaultLocale || 'en'];
90
103
  if (!messages) return key;
91
104
 
92
- // Handle Webpack / ES module JSON interop where the object is under .default
93
105
  if (messages.default && typeof messages.default === 'object') {
94
106
  messages = messages.default;
95
107
  }
@@ -109,117 +121,15 @@ export function createBro(globalConfig = {}) {
109
121
  };
110
122
 
111
123
  function defineRoute(config) {
124
+ // Register route for OpenAPI in Next environments
125
+ routeRegistry.register(config);
126
+
112
127
  return async function (req, context) {
113
128
  try {
114
129
  await ensureInitialized();
115
130
 
116
131
  const resolvedLocale = resolveLocale(Object.fromEntries(req.headers.entries()));
117
-
118
- // Auth extraction early for Identity caching
119
- let user = null;
120
- let apiKeyUsed = null;
121
- if (config.auth) {
122
- if (config.auth === 'api-key') {
123
- const apiKey = req.headers.get('x-api-key') || req.headers.get('authorization')?.replace('Bearer ', '');
124
- const configuredKey = globalConfig?.auth?.apiKey || process.env.API_KEY;
125
-
126
- let isValid = false;
127
- if (configuredKey) {
128
- const keys = (Array.isArray(configuredKey) ? configuredKey : configuredKey.split(',')).map(k => String(k).trim());
129
- isValid = keys.includes(apiKey);
130
- }
131
-
132
- if (!isValid) {
133
- return NextResponse.json({ error: 'Unauthorized', details: 'Missing or invalid API key' }, { status: 401 });
134
- }
135
- apiKeyUsed = apiKey;
136
- } else {
137
- const authHeader = req.headers.get('authorization');
138
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
139
- return NextResponse.json({ error: 'Unauthorized', details: 'Missing Bearer token' }, { status: 401 });
140
- }
141
-
142
- const token = authHeader.split(' ')[1];
143
- const secret = globalConfig?.auth?.jwtSecret || process.env.JWT_SECRET;
144
-
145
- if (!secret) {
146
- return NextResponse.json({ error: 'Internal Server Error', details: 'JWT_SECRET is not configured' }, { status: 500 });
147
- }
148
-
149
- const decoded = verifyJwt(token, secret);
150
- if (!decoded.valid) {
151
- return NextResponse.json({ error: 'Unauthorized', details: decoded.error }, { status: 401 });
152
- }
153
-
154
- user = decoded.payload;
155
-
156
- if (Array.isArray(config.auth) && config.auth.length > 0) {
157
- if (!user.role || !config.auth.includes(user.role)) {
158
- return NextResponse.json({ error: 'Forbidden', details: 'Insufficient permissions' }, { status: 403 });
159
- }
160
- }
161
- }
162
- }
163
-
164
- // Rate Limiting
165
- const activeRateLimit = config.rateLimit === false ? null : (config.rateLimit || globalConfig.rateLimit);
166
- if (activeRateLimit) {
167
- const ip = req.headers.get('x-forwarded-for') || 'ip';
168
- const urlObj = new URL(req.url);
169
- const rlKey = `rate-limit:${urlObj.pathname}:${ip}`;
170
-
171
- if (globalStore.__broRedis) {
172
- const currentCount = await globalStore.__broRedis.incr(rlKey);
173
- if (currentCount === 1) {
174
- await globalStore.__broRedis.expire(rlKey, Math.ceil(activeRateLimit.windowMs / 1000));
175
- }
176
- if (currentCount > activeRateLimit.max) {
177
- return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 });
178
- }
179
- } else {
180
- const now = Date.now();
181
- let record = globalStore.__broMemoryRateLimit.get(rlKey);
182
-
183
- if (!record || now > record.expires) {
184
- record = { count: 0, expires: now + activeRateLimit.windowMs };
185
- }
186
-
187
- record.count++;
188
- globalStore.__broMemoryRateLimit.set(rlKey, record);
189
-
190
- if (record.count > activeRateLimit.max) {
191
- return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 });
192
- }
193
- }
194
- }
195
-
196
- // Caching
197
- let cacheKey = null;
198
- if (config.cache && req.method === 'GET') {
199
- const urlObj = new URL(req.url);
200
- const identity = user ? (user.id || user.role || 'user') : (apiKeyUsed || 'anon');
201
- cacheKey = `cache:${urlObj.pathname}${urlObj.search}:${resolvedLocale}:${identity}`;
202
-
203
- if (globalStore.__broRedis) {
204
- const cachedData = await globalStore.__broRedis.get(cacheKey);
205
- if (cachedData) {
206
- return NextResponse.json(JSON.parse(cachedData), { status: 200 });
207
- }
208
- } else {
209
- const cached = globalStore.__broMemoryCache.get(cacheKey);
210
- if (cached) {
211
- if (Date.now() < cached.expires) {
212
- return NextResponse.json(cached.data, { status: 200 });
213
- } else {
214
- globalStore.__broMemoryCache.delete(cacheKey);
215
- }
216
- }
217
- }
218
- }
219
-
220
- const rawParams = context?.params ? await context.params : {};
221
132
  const url = new URL(req.url);
222
- const rawQuery = Object.fromEntries(url.searchParams.entries());
223
133
 
224
134
  let rawBody = {};
225
135
  const parsedFiles = {};
@@ -248,26 +158,6 @@ export function createBro(globalConfig = {}) {
248
158
  }
249
159
  }
250
160
 
251
- let body, params, query;
252
- try {
253
- if (config.body) body = await config.body.parseAsync(rawBody);
254
- } catch (err) {
255
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid Request Body', details: err.issues }, { status: 400 });
256
- throw err;
257
- }
258
- try {
259
- if (config.params) params = await config.params.parseAsync(rawParams);
260
- } catch (err) {
261
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid URL Parameters', details: err.issues }, { status: 400 });
262
- throw err;
263
- }
264
- try {
265
- if (config.query) query = await config.query.parseAsync(rawQuery);
266
- } catch (err) {
267
- if (err instanceof z.ZodError) return NextResponse.json({ error: 'Invalid Query Parameters', details: err.issues }, { status: 400 });
268
- throw err;
269
- }
270
-
271
161
  let ctxFile = undefined;
272
162
  let ctxFiles = undefined;
273
163
  if (totalFiles === 1) {
@@ -278,46 +168,82 @@ export function createBro(globalConfig = {}) {
278
168
  ctxFiles = parsedFiles;
279
169
  }
280
170
 
281
- const ctx = {
282
- req,
283
- 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
284
219
  db: globalDb,
285
- redis: globalStore.__broRedis,
220
+ redis: redisFallback,
286
221
  io: { emit: () => console.warn('[bro.js/next] WebSockets require standard bro.js server.') },
287
- body,
288
- params,
289
- query,
290
- file: ctxFile,
291
- files: ctxFiles,
292
- locale: resolvedLocale,
293
222
  t: (key, values) => translate(resolvedLocale, key, values),
294
- user,
295
- jwt: {
296
- sign: (payload, opts) => signJwt(payload, globalConfig?.auth?.jwtSecret || process.env.JWT_SECRET, Object.assign({ expiresIn: globalConfig?.auth?.expiresIn || '1d' }, opts || {}))
297
- },
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 || {})) },
298
225
  error: errorHelper
299
226
  };
300
227
 
301
- const result = await config.handler(ctx);
302
-
303
- if (cacheKey) {
304
- if (globalStore.__broRedis) {
305
- await globalStore.__broRedis.set(cacheKey, JSON.stringify(result), { EX: config.cache });
306
- } else {
307
- globalStore.__broMemoryCache.set(cacheKey, { data: result, expires: Date.now() + (config.cache * 1000) });
308
- }
309
- }
228
+ const response = await executeRequest(config, requestData, globalConfig, ctxExtras);
310
229
 
311
- return NextResponse.json(result, { status: 200 });
230
+ return NextResponse.json(response.body, {
231
+ status: response.status,
232
+ headers: response.headers
233
+ });
312
234
 
313
235
  } catch (error) {
314
- if (error.status) {
315
- return NextResponse.json({ error: error.message }, { status: error.status });
316
- }
317
236
  console.error('[bro.js/next] Unhandled Error:', error);
318
237
  return NextResponse.json(
319
- { error: 'Internal Server Error', message: error.message },
320
- { 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' } }
321
247
  );
322
248
  }
323
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 = [];