@memberjunction/server-extensions-core 0.0.1 → 5.17.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/README.md CHANGED
@@ -1,45 +1,175 @@
1
1
  # @memberjunction/server-extensions-core
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Plugin architecture for MJServer that enables auto-discovery and lifecycle management of extension modules. Extensions register Express routes, handle their own authentication, and participate in health checks and graceful shutdown — all without modifying MJServer source code.
4
+
5
+ ## Overview
6
+
7
+ This package provides two main exports:
4
8
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
9
+ - **`BaseServerExtension`** — Abstract base class that all extensions implement. Defines the `Initialize`, `Shutdown`, and `HealthCheck` lifecycle methods.
10
+ - **`ServerExtensionLoader`** — Discovers registered extension classes via MJ's `ClassFactory`, matches them to config entries, and manages their lifecycle.
6
11
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
12
+ Extensions are discovered automatically using MemberJunction's standard `@RegisterClass` + `ClassFactory` pattern. You register your extension class, add an entry to `mj.config.cjs`, and MJServer loads it at startup — zero source code changes to MJServer required per new extension.
8
13
 
9
- ## Purpose
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @memberjunction/server-extensions-core
18
+ ```
19
+
20
+ ## Quick Start
10
21
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/server-extensions-core`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
22
+ ### 1. Create an Extension
23
+
24
+ ```typescript
25
+ import { Application } from 'express';
26
+ import { RegisterClass } from '@memberjunction/global';
27
+ import {
28
+ BaseServerExtension,
29
+ ServerExtensionConfig,
30
+ ExtensionInitResult,
31
+ ExtensionHealthResult
32
+ } from '@memberjunction/server-extensions-core';
33
+
34
+ @RegisterClass(BaseServerExtension, 'MyCustomExtension')
35
+ export class MyCustomExtension extends BaseServerExtension {
36
+ async Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult> {
37
+ // Register your Express routes
38
+ app.get(config.RootPath + '/hello', (_req, res) => {
39
+ res.json({ message: 'Hello from my extension!' });
40
+ });
41
+
42
+ return {
43
+ Success: true,
44
+ Message: 'Custom extension loaded',
45
+ RegisteredRoutes: [`GET ${config.RootPath}/hello`]
46
+ };
47
+ }
48
+
49
+ async Shutdown(): Promise<void> {
50
+ // Clean up connections, drain requests, release resources
51
+ }
52
+
53
+ async HealthCheck(): Promise<ExtensionHealthResult> {
54
+ return { Healthy: true, Name: 'MyCustomExtension' };
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### 2. Configure in `mj.config.cjs`
60
+
61
+ ```javascript
62
+ module.exports = {
63
+ // ... other MJServer config ...
64
+ serverExtensions: [
65
+ {
66
+ Enabled: true,
67
+ DriverClass: 'MyCustomExtension',
68
+ RootPath: '/api/my-extension',
69
+ Settings: {
70
+ apiKey: process.env.MY_EXTENSION_API_KEY,
71
+ // Any extension-specific settings
72
+ }
73
+ }
74
+ ]
75
+ };
76
+ ```
15
77
 
16
- ## What is OIDC Trusted Publishing?
78
+ ### 3. Import Your Extension Package
17
79
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
80
+ Ensure your extension package is imported in your application so the `@RegisterClass` decorator fires at module load time. Add it as a dependency in your MJAPI project.
19
81
 
20
- ## Setup Instructions
82
+ ## API Reference
21
83
 
22
- To properly configure OIDC trusted publishing for this package:
84
+ ### `BaseServerExtension`
23
85
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
86
+ Abstract base class for all server extensions.
28
87
 
29
- ## DO NOT USE THIS PACKAGE
88
+ | Method | Description |
89
+ |--------|-------------|
90
+ | `Initialize(app, config)` | Called once at MJServer startup. Register routes, open connections. |
91
+ | `Shutdown()` | Called during graceful shutdown (SIGTERM/SIGINT). Clean up resources. |
92
+ | `HealthCheck()` | Called periodically. Return health status quickly (< 100ms). |
93
+ | `OnConfigurationChange?(config)` | Optional. Called when config changes at runtime. |
30
94
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
95
+ ### `ServerExtensionLoader`
36
96
 
37
- ## More Information
97
+ Manages the extension lifecycle.
38
98
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
99
+ | Method | Description |
100
+ |--------|-------------|
101
+ | `LoadExtensions(app, configs)` | Discover and initialize all enabled extensions from config. |
102
+ | `HealthCheckAll()` | Run health checks on all loaded extensions. |
103
+ | `ShutdownAll()` | Shut down all extensions in reverse order (LIFO). |
104
+ | `Extensions` | Read-only array of loaded extension instances. |
105
+ | `ExtensionCount` | Number of currently loaded extensions. |
42
106
 
43
- ---
107
+ ### Type Interfaces
108
+
109
+ #### `ServerExtensionConfig`
110
+
111
+ ```typescript
112
+ interface ServerExtensionConfig {
113
+ Enabled: boolean; // Skip loading if false
114
+ DriverClass: string; // Must match @RegisterClass key
115
+ RootPath: string; // URL prefix for extension routes
116
+ Settings: Record<string, unknown>; // Extension-specific config
117
+ }
118
+ ```
119
+
120
+ #### `ExtensionInitResult`
121
+
122
+ ```typescript
123
+ interface ExtensionInitResult {
124
+ Success: boolean;
125
+ Message: string;
126
+ RegisteredRoutes?: string[];
127
+ }
128
+ ```
129
+
130
+ #### `ExtensionHealthResult`
131
+
132
+ ```typescript
133
+ interface ExtensionHealthResult {
134
+ Healthy: boolean;
135
+ Name: string;
136
+ Details?: Record<string, unknown>;
137
+ }
138
+ ```
139
+
140
+ ## Lifecycle
44
141
 
45
- **Maintained for OIDC setup purposes only**
142
+ 1. MJServer reads `serverExtensions[]` from `mj.config.cjs`
143
+ 2. For each enabled entry, `ServerExtensionLoader` uses `ClassFactory.CreateInstance(BaseServerExtension, driverClass)` to find the registered class
144
+ 3. Creates an instance and calls `Initialize(app, config)`
145
+ 4. Extension registers its Express routes under `config.RootPath`
146
+ 5. MJServer exposes `GET /health/extensions` for aggregate health checks
147
+ 6. On SIGTERM/SIGINT, `ShutdownAll()` calls each extension's `Shutdown()` in reverse order
148
+
149
+ ## Error Handling
150
+
151
+ - Extensions that fail to initialize are logged and skipped — they don't prevent other extensions from loading
152
+ - Health check exceptions are caught and reported as unhealthy
153
+ - Shutdown exceptions are logged but don't prevent other extensions from shutting down
154
+
155
+ ## Authentication
156
+
157
+ Extensions handle their own authentication by default. Common patterns:
158
+
159
+ - **Platform-specific auth** (Slack HMAC signatures, Teams Bot Framework JWT)
160
+ - **MJServer auth middleware** — import from `@memberjunction/server` if you want to reuse MJServer's built-in auth
161
+ - **Custom auth** — API keys, OAuth, etc.
162
+
163
+ This is opt-in — extensions are not forced to use MJServer's auth middleware.
164
+
165
+ ## Testing
166
+
167
+ ```bash
168
+ npm run test # Run all tests
169
+ npm run test:watch # Watch mode
170
+ ```
171
+
172
+ ## Related Packages
173
+
174
+ - [`@memberjunction/messaging-adapters`](../MessagingAdapters/) — Slack and Teams adapters built on this framework
175
+ - [`@memberjunction/server`](../MJServer/) — MJServer that loads and manages extensions
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Abstract base class for MJServer extensions.
4
+ */
5
+ import { Application } from 'express';
6
+ import { ServerExtensionConfig, ExtensionInitResult, ExtensionHealthResult } from './types.js';
7
+ /**
8
+ * Abstract base class for MJServer extensions.
9
+ *
10
+ * Extensions are discovered via `@RegisterClass(BaseServerExtension, 'DriverClassName')`
11
+ * and matched to config entries in `mj.config.cjs` by their `DriverClass` name.
12
+ *
13
+ * MJServer calls `Initialize()` during startup, passing the Express app so the
14
+ * extension can register its own routes, middleware, and lifecycle hooks.
15
+ *
16
+ * ## Lifecycle
17
+ *
18
+ * 1. MJServer reads `serverExtensions[]` from `mj.config.cjs`
19
+ * 2. For each enabled entry, looks up `@RegisterClass(BaseServerExtension, driverClass)`
20
+ * 3. Creates instance via `ClassFactory.CreateInstance()`
21
+ * 4. Calls `Initialize(app, config)` — extension registers routes
22
+ * 5. Periodic `HealthCheck()` calls for monitoring
23
+ * 6. On server shutdown, calls `Shutdown()` for cleanup
24
+ *
25
+ * ## Usage
26
+ *
27
+ * ```typescript
28
+ * import { RegisterClass } from '@memberjunction/global';
29
+ * import { BaseServerExtension, ServerExtensionConfig, ExtensionInitResult } from '@memberjunction/server-extensions-core';
30
+ *
31
+ * @RegisterClass(BaseServerExtension, 'MyCustomExtension')
32
+ * export class MyCustomExtension extends BaseServerExtension {
33
+ * async Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult> {
34
+ * app.get(config.RootPath + '/hello', (_req, res) => {
35
+ * res.json({ message: 'Hello from my extension!' });
36
+ * });
37
+ * return { Success: true, Message: 'Custom extension loaded', RegisteredRoutes: [`GET ${config.RootPath}/hello`] };
38
+ * }
39
+ *
40
+ * async Shutdown(): Promise<void> {
41
+ * // Clean up resources
42
+ * }
43
+ *
44
+ * async HealthCheck(): Promise<ExtensionHealthResult> {
45
+ * return { Healthy: true, Name: 'MyCustomExtension' };
46
+ * }
47
+ * }
48
+ * ```
49
+ *
50
+ * ## Auth Middleware
51
+ *
52
+ * Extensions handle their own authentication by default. If you want to leverage
53
+ * MJServer's built-in auth middleware, import it from `@memberjunction/server`:
54
+ *
55
+ * ```typescript
56
+ * import { getSystemUser, verifyUserRecord } from '@memberjunction/server';
57
+ * ```
58
+ *
59
+ * This is opt-in — extensions like Slack/Teams use platform-specific auth
60
+ * (signature verification, Bot Framework JWT) instead.
61
+ */
62
+ export declare abstract class BaseServerExtension {
63
+ /**
64
+ * Initialize the extension. Called once during MJServer startup.
65
+ *
66
+ * Use this to register Express routes, set up WebSocket handlers,
67
+ * initialize connections, and prepare the extension for operation.
68
+ *
69
+ * @param app - The Express application instance to register routes on.
70
+ * Routes should be registered under `config.RootPath`.
71
+ * @param config - Extension-specific configuration from `mj.config.cjs`.
72
+ * The `Settings` object contains extension-specific config.
73
+ * @returns A result indicating whether initialization succeeded.
74
+ * On failure, the extension is skipped but other extensions still load.
75
+ */
76
+ abstract Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult>;
77
+ /**
78
+ * Graceful shutdown. Called when MJServer is shutting down (SIGTERM/SIGINT).
79
+ *
80
+ * Clean up connections, drain in-flight requests, close WebSocket connections,
81
+ * and release any resources held by the extension.
82
+ *
83
+ * This method should complete within a reasonable timeout (< 5 seconds).
84
+ * MJServer enforces a 10-second forced shutdown if graceful shutdown hangs.
85
+ */
86
+ abstract Shutdown(): Promise<void>;
87
+ /**
88
+ * Health check for this extension.
89
+ *
90
+ * Called by MJServer's aggregate `/health/extensions` endpoint.
91
+ * Should be fast (< 100ms) and non-blocking.
92
+ *
93
+ * @returns Health status including whether the extension is operational.
94
+ */
95
+ abstract HealthCheck(): Promise<ExtensionHealthResult>;
96
+ /**
97
+ * Optional: Called when configuration changes at runtime.
98
+ *
99
+ * Not all extensions need to support hot-reloading of configuration.
100
+ * Override this method if your extension can dynamically adjust its
101
+ * behavior without a full restart.
102
+ *
103
+ * @param newConfig - The updated configuration from `mj.config.cjs`.
104
+ */
105
+ OnConfigurationChange?(newConfig: ServerExtensionConfig): Promise<void>;
106
+ }
107
+ //# sourceMappingURL=BaseServerExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseServerExtension.d.ts","sourceRoot":"","sources":["../src/BaseServerExtension.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,8BAAsB,mBAAmB;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAElG;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAElC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAEtD;;;;;;;;OAQG;IACH,qBAAqB,CAAC,CAAC,SAAS,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;CAC1E"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Abstract base class for MJServer extensions.
4
+ */
5
+ /**
6
+ * Abstract base class for MJServer extensions.
7
+ *
8
+ * Extensions are discovered via `@RegisterClass(BaseServerExtension, 'DriverClassName')`
9
+ * and matched to config entries in `mj.config.cjs` by their `DriverClass` name.
10
+ *
11
+ * MJServer calls `Initialize()` during startup, passing the Express app so the
12
+ * extension can register its own routes, middleware, and lifecycle hooks.
13
+ *
14
+ * ## Lifecycle
15
+ *
16
+ * 1. MJServer reads `serverExtensions[]` from `mj.config.cjs`
17
+ * 2. For each enabled entry, looks up `@RegisterClass(BaseServerExtension, driverClass)`
18
+ * 3. Creates instance via `ClassFactory.CreateInstance()`
19
+ * 4. Calls `Initialize(app, config)` — extension registers routes
20
+ * 5. Periodic `HealthCheck()` calls for monitoring
21
+ * 6. On server shutdown, calls `Shutdown()` for cleanup
22
+ *
23
+ * ## Usage
24
+ *
25
+ * ```typescript
26
+ * import { RegisterClass } from '@memberjunction/global';
27
+ * import { BaseServerExtension, ServerExtensionConfig, ExtensionInitResult } from '@memberjunction/server-extensions-core';
28
+ *
29
+ * @RegisterClass(BaseServerExtension, 'MyCustomExtension')
30
+ * export class MyCustomExtension extends BaseServerExtension {
31
+ * async Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult> {
32
+ * app.get(config.RootPath + '/hello', (_req, res) => {
33
+ * res.json({ message: 'Hello from my extension!' });
34
+ * });
35
+ * return { Success: true, Message: 'Custom extension loaded', RegisteredRoutes: [`GET ${config.RootPath}/hello`] };
36
+ * }
37
+ *
38
+ * async Shutdown(): Promise<void> {
39
+ * // Clean up resources
40
+ * }
41
+ *
42
+ * async HealthCheck(): Promise<ExtensionHealthResult> {
43
+ * return { Healthy: true, Name: 'MyCustomExtension' };
44
+ * }
45
+ * }
46
+ * ```
47
+ *
48
+ * ## Auth Middleware
49
+ *
50
+ * Extensions handle their own authentication by default. If you want to leverage
51
+ * MJServer's built-in auth middleware, import it from `@memberjunction/server`:
52
+ *
53
+ * ```typescript
54
+ * import { getSystemUser, verifyUserRecord } from '@memberjunction/server';
55
+ * ```
56
+ *
57
+ * This is opt-in — extensions like Slack/Teams use platform-specific auth
58
+ * (signature verification, Bot Framework JWT) instead.
59
+ */
60
+ export class BaseServerExtension {
61
+ }
62
+ //# sourceMappingURL=BaseServerExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseServerExtension.js","sourceRoot":"","sources":["../src/BaseServerExtension.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,OAAgB,mBAAmB;CA+CxC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Discovers, initializes, and manages server extensions.
4
+ */
5
+ import { Application } from 'express';
6
+ import { BaseServerExtension } from './BaseServerExtension.js';
7
+ import { ServerExtensionConfig, ExtensionHealthResult } from './types.js';
8
+ /**
9
+ * Discovers, initializes, and manages the lifecycle of server extensions.
10
+ *
11
+ * Called by MJServer's `serve()` function during startup. The loader reads the
12
+ * `serverExtensions` array from `mj.config.cjs`, uses MJ's `ClassFactory` to
13
+ * find registered extension classes, and calls `Initialize()` on each.
14
+ *
15
+ * ## Discovery Flow
16
+ *
17
+ * 1. Reads `serverExtensions[]` array from `mj.config.cjs`
18
+ * 2. For each enabled entry, uses `ClassFactory.CreateInstance(BaseServerExtension, driverClass)`
19
+ * 3. Creates an instance and calls `Initialize(app, config)`
20
+ * 4. Tracks all loaded extensions for health checks and shutdown
21
+ *
22
+ * ## Usage
23
+ *
24
+ * ```typescript
25
+ * import { ServerExtensionLoader } from '@memberjunction/server-extensions-core';
26
+ *
27
+ * const loader = new ServerExtensionLoader();
28
+ * await loader.LoadExtensions(app, configInfo.serverExtensions);
29
+ *
30
+ * // Health check
31
+ * const health = await loader.HealthCheckAll();
32
+ *
33
+ * // Graceful shutdown
34
+ * await loader.ShutdownAll();
35
+ * ```
36
+ */
37
+ export declare class ServerExtensionLoader {
38
+ private _loadedExtensions;
39
+ /**
40
+ * Load and initialize all enabled extensions from config.
41
+ *
42
+ * Extensions that fail to initialize are logged and skipped — they do not
43
+ * prevent other extensions from loading. This ensures one broken extension
44
+ * doesn't take down the entire server.
45
+ *
46
+ * @param app - Express application for route registration.
47
+ * @param extensionConfigs - Array of extension configs from `mj.config.cjs`.
48
+ */
49
+ LoadExtensions(app: Application, extensionConfigs: ServerExtensionConfig[]): Promise<void>;
50
+ /**
51
+ * Run health checks on all loaded extensions.
52
+ *
53
+ * Each extension's `HealthCheck()` is called independently. If one extension's
54
+ * health check throws, it is reported as unhealthy without affecting others.
55
+ *
56
+ * @returns Array of health results, one per loaded extension.
57
+ */
58
+ HealthCheckAll(): Promise<ExtensionHealthResult[]>;
59
+ /**
60
+ * Shut down all loaded extensions gracefully.
61
+ *
62
+ * Called during MJServer's shutdown sequence (SIGTERM/SIGINT).
63
+ * Extensions are shut down in reverse order of loading.
64
+ * Errors during shutdown are logged but do not prevent other extensions from shutting down.
65
+ */
66
+ ShutdownAll(): Promise<void>;
67
+ /**
68
+ * Get all loaded extension instances for inspection or testing.
69
+ *
70
+ * @returns Read-only array of loaded extensions with their driver class names.
71
+ */
72
+ get Extensions(): ReadonlyArray<{
73
+ Instance: BaseServerExtension;
74
+ DriverClass: string;
75
+ }>;
76
+ /**
77
+ * Get the number of currently loaded extensions.
78
+ */
79
+ get ExtensionCount(): number;
80
+ /**
81
+ * Load and initialize a single extension from config.
82
+ *
83
+ * Uses MJ's `ClassFactory` to look up the registered class by `DriverClass` name,
84
+ * creates an instance, and calls `Initialize()`. On failure, logs the error and
85
+ * continues without throwing.
86
+ */
87
+ private loadSingleExtension;
88
+ }
89
+ //# sourceMappingURL=ServerExtensionLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerExtensionLoader.d.ts","sourceRoot":"","sources":["../src/ServerExtensionLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EACH,qBAAqB,EACrB,qBAAqB,EACxB,MAAM,YAAY,CAAC;AAgBpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,iBAAiB,CAAyB;IAElD;;;;;;;;;OASG;IACU,cAAc,CACvB,GAAG,EAAE,WAAW,EAChB,gBAAgB,EAAE,qBAAqB,EAAE,GAC1C,OAAO,CAAC,IAAI,CAAC;IAkBhB;;;;;;;OAOG;IACU,cAAc,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAmB/D;;;;;;OAMG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAczC;;;;OAIG;IACH,IAAW,UAAU,IAAI,aAAa,CAAC;QAAE,QAAQ,EAAE,mBAAmB,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAE7F;IAED;;OAEG;IACH,IAAW,cAAc,IAAI,MAAM,CAElC;IAED;;;;;;OAMG;YACW,mBAAmB;CA0CpC"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Discovers, initializes, and manages server extensions.
4
+ */
5
+ import { MJGlobal } from '@memberjunction/global';
6
+ import { LogError, LogStatus } from '@memberjunction/core';
7
+ import { BaseServerExtension } from './BaseServerExtension.js';
8
+ /**
9
+ * Discovers, initializes, and manages the lifecycle of server extensions.
10
+ *
11
+ * Called by MJServer's `serve()` function during startup. The loader reads the
12
+ * `serverExtensions` array from `mj.config.cjs`, uses MJ's `ClassFactory` to
13
+ * find registered extension classes, and calls `Initialize()` on each.
14
+ *
15
+ * ## Discovery Flow
16
+ *
17
+ * 1. Reads `serverExtensions[]` array from `mj.config.cjs`
18
+ * 2. For each enabled entry, uses `ClassFactory.CreateInstance(BaseServerExtension, driverClass)`
19
+ * 3. Creates an instance and calls `Initialize(app, config)`
20
+ * 4. Tracks all loaded extensions for health checks and shutdown
21
+ *
22
+ * ## Usage
23
+ *
24
+ * ```typescript
25
+ * import { ServerExtensionLoader } from '@memberjunction/server-extensions-core';
26
+ *
27
+ * const loader = new ServerExtensionLoader();
28
+ * await loader.LoadExtensions(app, configInfo.serverExtensions);
29
+ *
30
+ * // Health check
31
+ * const health = await loader.HealthCheckAll();
32
+ *
33
+ * // Graceful shutdown
34
+ * await loader.ShutdownAll();
35
+ * ```
36
+ */
37
+ export class ServerExtensionLoader {
38
+ constructor() {
39
+ this._loadedExtensions = [];
40
+ }
41
+ /**
42
+ * Load and initialize all enabled extensions from config.
43
+ *
44
+ * Extensions that fail to initialize are logged and skipped — they do not
45
+ * prevent other extensions from loading. This ensures one broken extension
46
+ * doesn't take down the entire server.
47
+ *
48
+ * @param app - Express application for route registration.
49
+ * @param extensionConfigs - Array of extension configs from `mj.config.cjs`.
50
+ */
51
+ async LoadExtensions(app, extensionConfigs) {
52
+ if (!extensionConfigs || extensionConfigs.length === 0) {
53
+ LogStatus('No server extensions configured');
54
+ return;
55
+ }
56
+ for (const config of extensionConfigs) {
57
+ if (!config.Enabled) {
58
+ LogStatus(`Server extension '${config.DriverClass}' is disabled, skipping`);
59
+ continue;
60
+ }
61
+ await this.loadSingleExtension(app, config);
62
+ }
63
+ LogStatus(`Loaded ${this._loadedExtensions.length} server extension(s)`);
64
+ }
65
+ /**
66
+ * Run health checks on all loaded extensions.
67
+ *
68
+ * Each extension's `HealthCheck()` is called independently. If one extension's
69
+ * health check throws, it is reported as unhealthy without affecting others.
70
+ *
71
+ * @returns Array of health results, one per loaded extension.
72
+ */
73
+ async HealthCheckAll() {
74
+ const results = [];
75
+ for (const ext of this._loadedExtensions) {
76
+ try {
77
+ const health = await ext.Instance.HealthCheck();
78
+ results.push(health);
79
+ }
80
+ catch (error) {
81
+ results.push({
82
+ Healthy: false,
83
+ Name: ext.DriverClass,
84
+ Details: { error: error instanceof Error ? error.message : String(error) }
85
+ });
86
+ }
87
+ }
88
+ return results;
89
+ }
90
+ /**
91
+ * Shut down all loaded extensions gracefully.
92
+ *
93
+ * Called during MJServer's shutdown sequence (SIGTERM/SIGINT).
94
+ * Extensions are shut down in reverse order of loading.
95
+ * Errors during shutdown are logged but do not prevent other extensions from shutting down.
96
+ */
97
+ async ShutdownAll() {
98
+ // Shut down in reverse order of loading (LIFO)
99
+ for (let i = this._loadedExtensions.length - 1; i >= 0; i--) {
100
+ const ext = this._loadedExtensions[i];
101
+ try {
102
+ await ext.Instance.Shutdown();
103
+ LogStatus(`Server extension '${ext.DriverClass}' shut down`);
104
+ }
105
+ catch (error) {
106
+ LogError(`Error shutting down extension '${ext.DriverClass}':`, undefined, error);
107
+ }
108
+ }
109
+ this._loadedExtensions = [];
110
+ }
111
+ /**
112
+ * Get all loaded extension instances for inspection or testing.
113
+ *
114
+ * @returns Read-only array of loaded extensions with their driver class names.
115
+ */
116
+ get Extensions() {
117
+ return this._loadedExtensions;
118
+ }
119
+ /**
120
+ * Get the number of currently loaded extensions.
121
+ */
122
+ get ExtensionCount() {
123
+ return this._loadedExtensions.length;
124
+ }
125
+ /**
126
+ * Load and initialize a single extension from config.
127
+ *
128
+ * Uses MJ's `ClassFactory` to look up the registered class by `DriverClass` name,
129
+ * creates an instance, and calls `Initialize()`. On failure, logs the error and
130
+ * continues without throwing.
131
+ */
132
+ async loadSingleExtension(app, config) {
133
+ const driverClass = config.DriverClass;
134
+ if (!driverClass) {
135
+ LogError('Server extension config missing DriverClass, skipping');
136
+ return;
137
+ }
138
+ try {
139
+ // Use MJ's ClassFactory to find the registered extension class
140
+ const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseServerExtension, driverClass);
141
+ if (!instance) {
142
+ LogError(`Server extension '${driverClass}' not found in ClassFactory. ` +
143
+ `Ensure the package is imported and the class uses ` +
144
+ `@RegisterClass(BaseServerExtension, '${driverClass}')`);
145
+ return;
146
+ }
147
+ const result = await instance.Initialize(app, config);
148
+ if (result.Success) {
149
+ this._loadedExtensions.push({ Instance: instance, Config: config, DriverClass: driverClass });
150
+ LogStatus(`Server extension '${driverClass}' initialized: ${result.Message}`);
151
+ if (result.RegisteredRoutes && result.RegisteredRoutes.length > 0) {
152
+ LogStatus(` Routes: ${result.RegisteredRoutes.join(', ')}`);
153
+ }
154
+ }
155
+ else {
156
+ LogError(`Server extension '${driverClass}' failed to initialize: ${result.Message}`);
157
+ }
158
+ }
159
+ catch (error) {
160
+ LogError(`Error loading server extension '${driverClass}':`, undefined, error);
161
+ }
162
+ }
163
+ }
164
+ //# sourceMappingURL=ServerExtensionLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerExtensionLoader.js","sourceRoot":"","sources":["../src/ServerExtensionLoader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ *
4
+ * Server Extension framework for MJServer. Provides the abstract base class
5
+ * and loader for auto-discovering and managing server extension plugins.
6
+ *
7
+ * ## Overview
8
+ *
9
+ * This package defines the plugin architecture that allows any package to register
10
+ * Express routes and lifecycle hooks on a running MJServer instance. Extensions
11
+ * are discovered via MJ's standard `@RegisterClass` + `ClassFactory` pattern and
12
+ * configured in `mj.config.cjs`.
13
+ *
14
+ * ## Quick Start
15
+ *
16
+ * ```typescript
17
+ * import { RegisterClass } from '@memberjunction/global';
18
+ * import { BaseServerExtension } from '@memberjunction/server-extensions-core';
19
+ *
20
+ * @RegisterClass(BaseServerExtension, 'MyExtension')
21
+ * export class MyExtension extends BaseServerExtension {
22
+ * // ... implement Initialize, Shutdown, HealthCheck
23
+ * }
24
+ * ```
25
+ *
26
+ * @see {@link BaseServerExtension} for the extension base class
27
+ * @see {@link ServerExtensionLoader} for the discovery and lifecycle manager
28
+ */
29
+ export { BaseServerExtension } from './BaseServerExtension.js';
30
+ export { ServerExtensionLoader } from './ServerExtensionLoader.js';
31
+ export { ServerExtensionConfig, ExtensionInitResult, ExtensionHealthResult, } from './types.js';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EACH,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,GACxB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ *
4
+ * Server Extension framework for MJServer. Provides the abstract base class
5
+ * and loader for auto-discovering and managing server extension plugins.
6
+ *
7
+ * ## Overview
8
+ *
9
+ * This package defines the plugin architecture that allows any package to register
10
+ * Express routes and lifecycle hooks on a running MJServer instance. Extensions
11
+ * are discovered via MJ's standard `@RegisterClass` + `ClassFactory` pattern and
12
+ * configured in `mj.config.cjs`.
13
+ *
14
+ * ## Quick Start
15
+ *
16
+ * ```typescript
17
+ * import { RegisterClass } from '@memberjunction/global';
18
+ * import { BaseServerExtension } from '@memberjunction/server-extensions-core';
19
+ *
20
+ * @RegisterClass(BaseServerExtension, 'MyExtension')
21
+ * export class MyExtension extends BaseServerExtension {
22
+ * // ... implement Initialize, Shutdown, HealthCheck
23
+ * }
24
+ * ```
25
+ *
26
+ * @see {@link BaseServerExtension} for the extension base class
27
+ * @see {@link ServerExtensionLoader} for the discovery and lifecycle manager
28
+ */
29
+ export { BaseServerExtension } from './BaseServerExtension.js';
30
+ export { ServerExtensionLoader } from './ServerExtensionLoader.js';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Core types for the MJServer extension framework.
4
+ */
5
+ /**
6
+ * Configuration for a server extension instance, loaded from the `serverExtensions`
7
+ * array in `mj.config.cjs`.
8
+ *
9
+ * Each entry in the array corresponds to one extension instance. The `DriverClass`
10
+ * field is used to look up the registered class via `ClassFactory.CreateInstance()`.
11
+ *
12
+ * @example
13
+ * ```javascript
14
+ * // mj.config.cjs
15
+ * module.exports = {
16
+ * serverExtensions: [
17
+ * {
18
+ * Enabled: true,
19
+ * DriverClass: 'SlackMessagingExtension',
20
+ * RootPath: '/webhook/slack',
21
+ * Settings: {
22
+ * AgentID: '...',
23
+ * BotToken: process.env.SLACK_BOT_TOKEN,
24
+ * }
25
+ * }
26
+ * ]
27
+ * };
28
+ * ```
29
+ */
30
+ export interface ServerExtensionConfig {
31
+ /** Whether this extension is enabled. Disabled extensions are skipped during loading. */
32
+ Enabled: boolean;
33
+ /**
34
+ * The `@RegisterClass` key used to look up this extension in ClassFactory.
35
+ * Must match the second argument of `@RegisterClass(BaseServerExtension, 'DriverClass')`.
36
+ */
37
+ DriverClass: string;
38
+ /**
39
+ * URL path prefix for this extension's routes (e.g., `'/webhook/slack'`).
40
+ * The extension registers its routes under this prefix on the Express app.
41
+ */
42
+ RootPath: string;
43
+ /**
44
+ * Extension-specific configuration. The shape varies by extension type.
45
+ * For messaging adapters, this contains `AgentID`, `BotToken`, etc.
46
+ * The extension is responsible for parsing and validating its own settings.
47
+ */
48
+ Settings: Record<string, unknown>;
49
+ }
50
+ /**
51
+ * Result returned from extension initialization.
52
+ * Extensions report whether startup succeeded and what routes they registered.
53
+ */
54
+ export interface ExtensionInitResult {
55
+ /** Whether initialization succeeded. If `false`, the extension is not loaded. */
56
+ Success: boolean;
57
+ /** Human-readable status message, logged by the extension loader. */
58
+ Message: string;
59
+ /**
60
+ * Routes registered by this extension. Used for logging and health check reporting.
61
+ * @example `['POST /webhook/slack', 'GET /webhook/slack/health']`
62
+ */
63
+ RegisteredRoutes?: string[];
64
+ }
65
+ /**
66
+ * Health check result for a single extension.
67
+ * Returned by `BaseServerExtension.HealthCheck()` and aggregated by `ServerExtensionLoader`.
68
+ */
69
+ export interface ExtensionHealthResult {
70
+ /** Whether the extension is healthy and able to process requests. */
71
+ Healthy: boolean;
72
+ /** Human-readable name of the extension (typically the DriverClass). */
73
+ Name: string;
74
+ /** Optional details about the health status (uptime, last error, queue depth, etc.). */
75
+ Details?: Record<string, unknown>;
76
+ }
77
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,qBAAqB;IAClC,yFAAyF;IACzF,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IAEjB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IAEjB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IAEb,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module @memberjunction/server-extensions-core
3
+ * @description Core types for the MJServer extension framework.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json CHANGED
@@ -1,10 +1,33 @@
1
1
  {
2
2
  "name": "@memberjunction/server-extensions-core",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/server-extensions-core",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
3
+ "version": "5.17.0",
4
+ "description": "MemberJunction: Server Extension framework - plugin architecture for MJServer that enables auto-discovery and lifecycle management of extension modules",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "/dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc && tsc-alias -f",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "test:coverage": "vitest run --coverage"
16
+ },
17
+ "author": "MemberJunction.com",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@memberjunction/global": "5.17.0",
21
+ "@memberjunction/core": "5.17.0",
22
+ "express": "^5.2.1"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.9.3",
26
+ "vitest": "^3.1.1",
27
+ "vite-tsconfig-paths": "^5.1.4"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/MemberJunction/MJ"
32
+ }
10
33
  }