@memberjunction/server 5.12.0 → 5.14.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 (53) hide show
  1. package/dist/agents/skip-sdk.d.ts +8 -0
  2. package/dist/agents/skip-sdk.d.ts.map +1 -1
  3. package/dist/agents/skip-sdk.js +19 -0
  4. package/dist/agents/skip-sdk.js.map +1 -1
  5. package/dist/index.d.ts +4 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +94 -89
  8. package/dist/index.js.map +1 -1
  9. package/dist/middleware/BaseServerMiddleware.d.ts +103 -0
  10. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -0
  11. package/dist/middleware/BaseServerMiddleware.js +104 -0
  12. package/dist/middleware/BaseServerMiddleware.js.map +1 -0
  13. package/dist/middleware/MJTenantFilterMiddleware.d.ts +22 -0
  14. package/dist/middleware/MJTenantFilterMiddleware.d.ts.map +1 -0
  15. package/dist/middleware/MJTenantFilterMiddleware.js +41 -0
  16. package/dist/middleware/MJTenantFilterMiddleware.js.map +1 -0
  17. package/dist/middleware/index.d.ts +3 -0
  18. package/dist/middleware/index.d.ts.map +1 -0
  19. package/dist/middleware/index.js +3 -0
  20. package/dist/middleware/index.js.map +1 -0
  21. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  22. package/dist/resolvers/GetDataResolver.js +2 -1
  23. package/dist/resolvers/GetDataResolver.js.map +1 -1
  24. package/dist/resolvers/{CreateQueryResolver.d.ts → QuerySystemUserResolver.d.ts} +26 -82
  25. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -0
  26. package/dist/resolvers/{CreateQueryResolver.js → QuerySystemUserResolver.js} +123 -486
  27. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -0
  28. package/dist/resolvers/TestQuerySQLResolver.d.ts +54 -0
  29. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -0
  30. package/dist/resolvers/TestQuerySQLResolver.js +189 -0
  31. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -0
  32. package/package.json +59 -59
  33. package/src/__tests__/bcsaas-integration.test.ts +1 -1
  34. package/src/agents/skip-sdk.ts +22 -0
  35. package/src/index.ts +111 -96
  36. package/src/middleware/BaseServerMiddleware.ts +141 -0
  37. package/src/middleware/MJTenantFilterMiddleware.ts +39 -0
  38. package/src/middleware/index.ts +2 -0
  39. package/src/resolvers/GetDataResolver.ts +2 -1
  40. package/src/resolvers/{CreateQueryResolver.ts → QuerySystemUserResolver.ts} +143 -413
  41. package/src/resolvers/TestQuerySQLResolver.ts +149 -0
  42. package/dist/hooks.d.ts +0 -65
  43. package/dist/hooks.d.ts.map +0 -1
  44. package/dist/hooks.js +0 -14
  45. package/dist/hooks.js.map +0 -1
  46. package/dist/resolvers/CreateQueryResolver.d.ts.map +0 -1
  47. package/dist/resolvers/CreateQueryResolver.js.map +0 -1
  48. package/dist/test-dynamic-plugin.d.ts +0 -6
  49. package/dist/test-dynamic-plugin.d.ts.map +0 -1
  50. package/dist/test-dynamic-plugin.js +0 -18
  51. package/dist/test-dynamic-plugin.js.map +0 -1
  52. package/src/hooks.ts +0 -77
  53. package/src/test-dynamic-plugin.ts +0 -36
package/src/index.ts CHANGED
@@ -99,13 +99,14 @@ export * from './resolvers/EntityRecordNameResolver.js';
99
99
  export * from './resolvers/MergeRecordsResolver.js';
100
100
  export * from './resolvers/ReportResolver.js';
101
101
  export * from './resolvers/QueryResolver.js';
102
+ export * from './resolvers/TestQuerySQLResolver.js';
102
103
  export * from './resolvers/SqlLoggingConfigResolver.js';
103
104
  export * from './resolvers/SyncRolesUsersResolver.js';
104
105
  export * from './resolvers/SyncDataResolver.js';
105
106
  export * from './resolvers/GetDataResolver.js';
106
107
  export * from './resolvers/GetDataContextDataResolver.js';
107
108
  export * from './resolvers/TransactionGroupResolver.js';
108
- export * from './resolvers/CreateQueryResolver.js';
109
+ export * from './resolvers/QuerySystemUserResolver.js';
109
110
  export * from './resolvers/TelemetryResolver.js';
110
111
  export * from './resolvers/APIKeyResolver.js';
111
112
  export * from './resolvers/MCPResolver.js';
@@ -126,32 +127,16 @@ export * from './resolvers/CurrentUserContextResolver.js';
126
127
  export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
127
128
 
128
129
  export * from './generated/generated.js';
129
- export * from './hooks.js';
130
130
  export * from './multiTenancy/index.js';
131
+ export * from './middleware/index.js';
131
132
 
132
- import type { ServerExtensibilityOptions, HookWithOptions } from './hooks.js';
133
- import { HookRegistry } from '@memberjunction/core';
134
- import type { HookRegistrationOptions } from '@memberjunction/core';
133
+ import { RegisterDataHook } from '@memberjunction/core';
134
+ import type { RequestHandler, ErrorRequestHandler } from 'express';
135
135
  import type { ApolloServerPlugin } from '@apollo/server';
136
- import { createTenantMiddleware, createTenantPreRunViewHook, createTenantPreSaveHook } from './multiTenancy/index.js';
136
+ import type { GraphQLSchema } from 'graphql';
137
+ import { BaseServerMiddleware } from './middleware/BaseServerMiddleware.js';
137
138
 
138
- /**
139
- * Register a hook that may be a plain function or a `{ hook, Priority, Namespace }` object.
140
- * Dynamic packages (e.g., BCSaaS) return hooks in object form to declare registration metadata.
141
- */
142
- function registerHookEntry<T>(hookName: string, entry: T | HookWithOptions<T>): void {
143
- if (typeof entry === 'function') {
144
- HookRegistry.Register(hookName, entry);
145
- } else if (entry && typeof entry === 'object' && 'hook' in entry) {
146
- const { hook, Priority, Namespace } = entry as HookWithOptions<T>;
147
- const options: HookRegistrationOptions = {};
148
- if (Priority != null) options.Priority = Priority;
149
- if (Namespace != null) options.Namespace = Namespace;
150
- HookRegistry.Register(hookName, hook, options);
151
- }
152
- }
153
-
154
- export type MJServerOptions = ServerExtensibilityOptions & {
139
+ export type MJServerOptions = {
155
140
  onBeforeServe?: () => void | Promise<void>;
156
141
  restApiOptions?: Partial<RESTApiOptions>; // Options for REST API configuration
157
142
  };
@@ -417,6 +402,91 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
417
402
  Object.values(module).filter((value) => typeof value === 'function')
418
403
  ) as BuildSchemaOptions['resolvers'];
419
404
 
405
+ // ─── Discover all server middleware via ClassFactory ─────────────────────
406
+ const allRegistrations = MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseServerMiddleware);
407
+
408
+ // Deduplicate by key (same key -> highest priority wins).
409
+ // This is the replacement mechanism: if BCSaaS registers with the same
410
+ // key as MJ's built-in tenant filter, ClassFactory's priority system
411
+ // ensures only the higher-priority one is used.
412
+ const uniqueKeys = new Set(
413
+ allRegistrations.map(r => r.Key?.trim().toLowerCase()).filter((k): k is string => k != null)
414
+ );
415
+
416
+ const winnerRegistrations: typeof allRegistrations = [];
417
+ for (const key of uniqueKeys) {
418
+ const winner = MJGlobal.Instance.ClassFactory.GetRegistration(BaseServerMiddleware, key);
419
+ if (winner) winnerRegistrations.push(winner);
420
+ }
421
+
422
+ // Instantiate and filter by Enabled
423
+ const middlewares: BaseServerMiddleware[] = [];
424
+ for (const reg of winnerRegistrations) {
425
+ const MwClass = reg.SubClass as new () => BaseServerMiddleware;
426
+ const mw = new MwClass();
427
+ if (mw.Enabled) {
428
+ middlewares.push(mw);
429
+ }
430
+ }
431
+
432
+ // Initialize all middleware
433
+ for (const mw of middlewares) {
434
+ await mw.Initialize();
435
+ console.log(` [Middleware] ${mw.Label}`);
436
+ }
437
+
438
+ // Collect middleware contributions for each pipeline stage
439
+ const mwPreAuth: RequestHandler[] = [];
440
+ const mwPostAuth: RequestHandler[] = [];
441
+ const mwPostRoute: (RequestHandler | ErrorRequestHandler)[] = [];
442
+ const mwApolloPlugins: ApolloServerPlugin[] = [];
443
+ const mwSchemaTransformers: ((schema: GraphQLSchema) => GraphQLSchema)[] = [];
444
+ const mwResolverPaths: string[] = [];
445
+
446
+ for (const mw of middlewares) {
447
+ mwPreAuth.push(...mw.GetPreAuthMiddleware());
448
+ mwPostAuth.push(...mw.GetPostAuthMiddleware());
449
+ mwPostRoute.push(...mw.GetPostRouteMiddleware());
450
+ mwApolloPlugins.push(...mw.GetApolloPlugins());
451
+ mwSchemaTransformers.push(...mw.GetSchemaTransformers());
452
+ mwResolverPaths.push(...mw.GetResolverPaths());
453
+
454
+ // Express app configuration escape hatch
455
+ if (mw.ConfigureExpressApp) {
456
+ await mw.ConfigureExpressApp(app);
457
+ }
458
+
459
+ // Extract hook methods and register in the global hook store
460
+ // (ProviderBase/BaseEntity will read these via GetDataHooks())
461
+ RegisterDataHook('PreRunView', mw.PreRunView.bind(mw));
462
+ RegisterDataHook('PostRunView', mw.PostRunView.bind(mw));
463
+ RegisterDataHook('PreSave', mw.PreSave.bind(mw));
464
+ }
465
+
466
+ // ─── Resolve middleware-contributed resolver paths and merge into resolvers ───
467
+ let allResolvers = resolvers;
468
+ if (mwResolverPaths.length > 0) {
469
+ const mwGlobs = mwResolverPaths.flatMap((p) => (isWindows ? p.replace(/\\/g, '/') : p));
470
+ const mwResolverFiles = fg.globSync(mwGlobs);
471
+ if (mwResolverFiles.length > 0) {
472
+ const mwModules = await Promise.all(
473
+ mwResolverFiles.map((modulePath) => {
474
+ try {
475
+ return import(isWindows ? `file://${modulePath}` : modulePath);
476
+ } catch (e) {
477
+ console.error(`Error loading middleware resolver at '${modulePath}'`, e);
478
+ throw e;
479
+ }
480
+ })
481
+ );
482
+ const mwResolvers = mwModules.flatMap((module) =>
483
+ Object.values(module).filter((value) => typeof value === 'function')
484
+ );
485
+ allResolvers = [...resolvers, ...mwResolvers] as BuildSchemaOptions['resolvers'];
486
+ console.log(` [Middleware Resolvers] Loaded ${mwResolverFiles.length} resolver file(s) from middleware`);
487
+ }
488
+ }
489
+
420
490
  // Create an explicit PubSub instance so we can reference it outside of resolvers
421
491
  // graphql-subscriptions v3 renamed asyncIterator→asyncIterableIterator, but
422
492
  // type-graphql still calls asyncIterator. Shim for compatibility.
@@ -451,7 +521,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
451
521
  let schema = mergeSchemas({
452
522
  schemas: [
453
523
  buildSchemaSync({
454
- resolvers,
524
+ resolvers: allResolvers,
455
525
  validate: false,
456
526
  scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
457
527
  emitSchemaFile: websiteRunFromPackage !== 1,
@@ -463,11 +533,9 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
463
533
  schema = requireSystemUserDirective.transformer(schema);
464
534
  schema = publicDirective.transformer(schema);
465
535
 
466
- // Apply user-provided schema transformers (after built-in directive transformers)
467
- if (options?.SchemaTransformers) {
468
- for (const transformer of options.SchemaTransformers) {
469
- schema = transformer(schema);
470
- }
536
+ // Apply middleware-contributed schema transformers (after built-in directive transformers)
537
+ for (const transformer of mwSchemaTransformers) {
538
+ schema = transformer(schema);
471
539
  }
472
540
 
473
541
  const httpServer = createServer(app);
@@ -502,7 +570,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
502
570
  const apolloServer = buildApolloServer(
503
571
  { schema },
504
572
  { httpServer, serverCleanup },
505
- options?.ApolloPlugins
573
+ mwApolloPlugins.length > 0 ? mwApolloPlugins : undefined
506
574
  );
507
575
  await apolloServer.start();
508
576
 
@@ -534,16 +602,9 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
534
602
  res.status(200).json({ status: 'ok' });
535
603
  });
536
604
 
537
- // Apply user-provided Express middleware (after compression, before routes)
538
- if (options?.ExpressMiddlewareBefore) {
539
- for (const mw of options.ExpressMiddlewareBefore) {
540
- app.use(mw);
541
- }
542
- }
543
-
544
- // Escape hatch for advanced Express app configuration
545
- if (options?.ConfigureExpressApp) {
546
- await Promise.resolve(options.ConfigureExpressApp(app));
605
+ // Apply middleware-contributed pre-auth handlers (after compression, before routes)
606
+ for (const mw of mwPreAuth) {
607
+ app.use(mw);
547
608
  }
548
609
 
549
610
  // ─── OAuth callback routes (unauthenticated, registered BEFORE auth) ─────
@@ -575,20 +636,12 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
575
636
  // ─── Unified auth middleware (replaces both REST authMiddleware and contextFunction auth) ─────
576
637
  app.use(createUnifiedAuthMiddleware(dataSources));
577
638
 
578
- // ─── Built-in post-auth middleware (multi-tenancy) ─────
579
- // Config-driven multi-tenancy middleware runs after auth so it can read req.userPayload.
580
- if (configInfo.multiTenancy?.enabled) {
581
- const tenantMiddleware = createTenantMiddleware(configInfo.multiTenancy);
582
- app.use(tenantMiddleware);
583
- }
584
-
585
- // ─── Post-auth middleware from plugins ─────
639
+ // ─── Post-auth middleware from BaseServerMiddleware plugins ─────
586
640
  // Middleware here has access to the authenticated user via req.userPayload.
587
- // Use this for tenant context resolution, org membership loading, etc.
588
- if (options?.ExpressMiddlewarePostAuth) {
589
- for (const mw of options.ExpressMiddlewarePostAuth) {
590
- app.use(mw);
591
- }
641
+ // Contributions come from @RegisterClass(BaseServerMiddleware, key) classes
642
+ // (e.g., MJTenantFilterMiddleware for multi-tenancy, BCSaaSMiddleware for org context).
643
+ for (const mw of mwPostAuth) {
644
+ app.use(mw);
592
645
  }
593
646
 
594
647
  // ─── OAuth authenticated routes (auth already handled by unified middleware) ─────
@@ -645,10 +698,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
645
698
  );
646
699
 
647
700
  // ─── Post-route middleware (error handlers, catch-alls) ─────
648
- if (options?.ExpressMiddlewareAfter) {
649
- for (const mw of options.ExpressMiddlewareAfter) {
650
- app.use(mw);
651
- }
701
+ for (const mw of mwPostRoute) {
702
+ app.use(mw);
652
703
  }
653
704
 
654
705
  // Initialize and start scheduled jobs service if enabled
@@ -664,45 +715,9 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
664
715
  }
665
716
  }
666
717
 
667
- // Register provider-level hooks with the global HookRegistry.
668
- // Each entry can be a plain function or a { hook, Priority, Namespace } object.
669
- if (options?.PreRunViewHooks) {
670
- for (const entry of options.PreRunViewHooks) {
671
- registerHookEntry('PreRunView', entry);
672
- }
673
- }
674
- if (options?.PostRunViewHooks) {
675
- for (const entry of options.PostRunViewHooks) {
676
- registerHookEntry('PostRunView', entry);
677
- }
678
- }
679
- if (options?.PreSaveHooks) {
680
- for (const entry of options.PreSaveHooks) {
681
- registerHookEntry('PreSave', entry);
682
- }
683
- }
684
-
685
- // Auto-register multi-tenancy hooks when enabled in config
686
- // (The tenant Express middleware was already registered above in the post-auth slot)
687
- if (configInfo.multiTenancy?.enabled) {
688
- console.log('[MultiTenancy] Enabled — registering tenant isolation hooks');
689
- const tenantConfig = configInfo.multiTenancy;
690
-
691
- // Register tenant PreRunView hook (injects WHERE clauses)
692
- // Priority 50 + namespace allows middle layers to replace with their own implementation
693
- HookRegistry.Register('PreRunView', createTenantPreRunViewHook(tenantConfig), {
694
- Priority: 50,
695
- Namespace: 'mj:tenantFilter',
696
- });
697
-
698
- // Register tenant PreSave hook (validates tenant column on writes)
699
- HookRegistry.Register('PreSave', createTenantPreSaveHook(tenantConfig), {
700
- Priority: 50,
701
- Namespace: 'mj:tenantSave',
702
- });
703
-
704
- console.log(`[MultiTenancy] Context source: ${tenantConfig.contextSource}, scoping: ${tenantConfig.scopingStrategy}, write protection: ${tenantConfig.writeProtection}`);
705
- }
718
+ // Data hooks are now registered via BaseServerMiddleware classes above
719
+ // (e.g., MJTenantFilterMiddleware registers PreRunView and PreSave hooks).
720
+ // No config-bag hook registration needed.
706
721
 
707
722
  if (options?.onBeforeServe) {
708
723
  await Promise.resolve(options.onBeforeServe());
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Base class for server middleware that intercepts the MJServer request pipeline.
3
+ *
4
+ * Subclasses register via @RegisterClass(BaseServerMiddleware, key) and are
5
+ * discovered by serve() via ClassFactory.GetAllRegistrations().
6
+ *
7
+ * Key-based deduplication: If two middleware classes register with the same key,
8
+ * ClassFactory returns the highest-priority one -- the other is replaced.
9
+ * This is how BCSaaS replaces MJ's built-in tenant filtering.
10
+ *
11
+ * For adding new routes/endpoints (webhooks, bot endpoints), see
12
+ * ServerExtensionsCore and BaseServerExtension (PR #2037).
13
+ * BaseServerMiddleware is for intercepting the existing pipeline, not adding to it.
14
+ */
15
+
16
+ import type { RequestHandler, ErrorRequestHandler, Application } from 'express';
17
+ import type { ApolloServerPlugin } from '@apollo/server';
18
+ import type { GraphQLSchema } from 'graphql';
19
+ import type { RunViewParams, UserInfo, BaseEntity, RunViewResult } from '@memberjunction/core';
20
+
21
+ export abstract class BaseServerMiddleware {
22
+ // --- Identity ---
23
+
24
+ /**
25
+ * Human-readable label for logging (e.g., 'mj:tenantFilter', 'bcsaas').
26
+ */
27
+ abstract get Label(): string;
28
+
29
+ /**
30
+ * Whether this middleware is active. Override to check config, env vars, etc.
31
+ * Disabled middleware classes are never instantiated or activated by serve().
32
+ * Default: true.
33
+ */
34
+ get Enabled(): boolean { return true; }
35
+
36
+ // --- Lifecycle ---
37
+
38
+ /**
39
+ * Optional async initialization (read config, warm caches, etc.).
40
+ * Called by serve() after instantiation, before middleware/hooks are extracted.
41
+ */
42
+ async Initialize(): Promise<void> { /* no-op by default */ }
43
+
44
+ // --- Express Middleware ---
45
+ // Each method corresponds to a named slot in the request pipeline.
46
+ // Override to contribute middleware at that stage.
47
+ // Default implementations return empty arrays (no-op).
48
+
49
+ /**
50
+ * Express middleware applied BEFORE authentication.
51
+ * Runs after compression but before OAuth/REST/GraphQL routes.
52
+ * Use for: rate limiting, request logging, custom headers.
53
+ */
54
+ GetPreAuthMiddleware(): RequestHandler[] { return []; }
55
+
56
+ /**
57
+ * Express middleware that runs AFTER authentication has resolved UserInfo.
58
+ * The authenticated user payload is available at req.userPayload.
59
+ * Use for: tenant context resolution, org membership loading.
60
+ */
61
+ GetPostAuthMiddleware(): RequestHandler[] { return []; }
62
+
63
+ /**
64
+ * Express middleware/error handlers applied AFTER all routes.
65
+ * Use for: catch-all error handlers, response logging.
66
+ */
67
+ GetPostRouteMiddleware(): (RequestHandler | ErrorRequestHandler)[] { return []; }
68
+
69
+ // --- Express App Configuration ---
70
+
71
+ /**
72
+ * Optional escape hatch for advanced Express app configuration.
73
+ * Called once during server setup with the Express app instance.
74
+ * Use for: trust proxy settings, custom CORS, body parser config.
75
+ * Return undefined to skip (default).
76
+ */
77
+ ConfigureExpressApp?(app: Application): void | Promise<void>;
78
+
79
+ // --- Apollo / GraphQL Extensions ---
80
+
81
+ /**
82
+ * Additional Apollo Server plugins merged with built-in plugins.
83
+ * Use for: query tracing, caching, custom error formatting.
84
+ */
85
+ GetApolloPlugins(): ApolloServerPlugin[] { return []; }
86
+
87
+ /**
88
+ * Schema transformers applied after built-in directive transformers.
89
+ * Use for: custom directives, schema stitching, field-level auth.
90
+ */
91
+ GetSchemaTransformers(): ((schema: GraphQLSchema) => GraphQLSchema)[] { return []; }
92
+
93
+ // --- Resolver Discovery ---
94
+
95
+ /**
96
+ * Additional TypeGraphQL resolver glob paths to include in the Apollo schema.
97
+ * serve() collects these from all active middleware and adds them to the
98
+ * resolvers array passed to buildSchema().
99
+ *
100
+ * Return absolute glob paths (use `path.join(__dirname, ...)` or equivalent).
101
+ *
102
+ * Use for: Open App resolvers, domain-specific GraphQL queries/mutations
103
+ * that live outside MJServer's built-in resolver set.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * GetResolverPaths(): string[] {
108
+ * return [path.join(__dirname, 'resolvers', '*Resolver.{js,ts}')];
109
+ * }
110
+ * ```
111
+ */
112
+ GetResolverPaths(): string[] { return []; }
113
+
114
+ // --- Data Hooks ---
115
+ // Override any of these to intercept data operations.
116
+ // Default implementations are pass-through (no-op).
117
+
118
+ /**
119
+ * Hook that runs before a RunView operation. Can modify the RunViewParams
120
+ * (e.g., injecting tenant filters) before execution.
121
+ */
122
+ PreRunView(params: RunViewParams, contextUser: UserInfo | undefined): RunViewParams | Promise<RunViewParams> {
123
+ return params;
124
+ }
125
+
126
+ /**
127
+ * Hook that runs after a RunView operation completes. Can modify the result
128
+ * (e.g., filtering or augmenting data) before it is returned to the caller.
129
+ */
130
+ PostRunView(params: RunViewParams, results: RunViewResult, contextUser: UserInfo | undefined): RunViewResult | Promise<RunViewResult> {
131
+ return results;
132
+ }
133
+
134
+ /**
135
+ * Hook that runs before a Save operation on a BaseEntity.
136
+ * Return true to allow, false to reject silently, or a string to reject with that error message.
137
+ */
138
+ PreSave(entity: BaseEntity, contextUser: UserInfo | undefined): boolean | string | Promise<boolean | string> {
139
+ return true;
140
+ }
141
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * MJ's built-in multi-tenancy middleware.
3
+ *
4
+ * Registers with key 'mj:tenantFilter' so that downstream layers (e.g., BCSaaS)
5
+ * can replace it by registering with the same key at higher ClassFactory priority.
6
+ *
7
+ * Conditionally enabled via configInfo.multiTenancy?.enabled.
8
+ */
9
+
10
+ import { RegisterClass } from '@memberjunction/global';
11
+ import { BaseServerMiddleware } from './BaseServerMiddleware.js';
12
+ import { configInfo } from '../config.js';
13
+ import { createTenantMiddleware, createTenantPreRunViewHook, createTenantPreSaveHook } from '../multiTenancy/index.js';
14
+ import type { RequestHandler } from 'express';
15
+ import type { RunViewParams, UserInfo, BaseEntity } from '@memberjunction/core';
16
+
17
+ @RegisterClass(BaseServerMiddleware, 'mj:tenantFilter')
18
+ export class MJTenantFilterMiddleware extends BaseServerMiddleware {
19
+ get Label(): string { return 'mj:tenantFilter'; }
20
+
21
+ /**
22
+ * Only active when multiTenancy is enabled in config.
23
+ */
24
+ get Enabled(): boolean {
25
+ return configInfo.multiTenancy?.enabled ?? false;
26
+ }
27
+
28
+ GetPostAuthMiddleware(): RequestHandler[] {
29
+ return [createTenantMiddleware(configInfo.multiTenancy!)];
30
+ }
31
+
32
+ PreRunView(params: RunViewParams, contextUser: UserInfo | undefined): RunViewParams | Promise<RunViewParams> {
33
+ return createTenantPreRunViewHook(configInfo.multiTenancy!)(params, contextUser);
34
+ }
35
+
36
+ PreSave(entity: BaseEntity, contextUser: UserInfo | undefined): boolean | string | Promise<boolean | string> {
37
+ return createTenantPreSaveHook(configInfo.multiTenancy!)(entity, contextUser);
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export * from './BaseServerMiddleware.js';
2
+ export * from './MJTenantFilterMiddleware.js';
@@ -1,6 +1,7 @@
1
1
  import { Arg, Ctx, Field, InputType, ObjectType, Query } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { LogError, LogStatus, Metadata, QueryCompositionEngine } from '@memberjunction/core';
3
+ import { LogError, LogStatus, Metadata } from '@memberjunction/core';
4
+ import { QueryCompositionEngine } from '@memberjunction/generic-database-provider';
4
5
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
6
  import { v4 as uuidv4 } from 'uuid';
6
7
  import { GetReadOnlyDataSource, GetReadOnlyProvider } from '../util.js';