@memberjunction/server-extensions-core 0.0.1 → 5.18.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 +158 -28
- package/dist/BaseServerExtension.d.ts +107 -0
- package/dist/BaseServerExtension.d.ts.map +1 -0
- package/dist/BaseServerExtension.js +62 -0
- package/dist/BaseServerExtension.js.map +1 -0
- package/dist/ServerExtensionLoader.d.ts +89 -0
- package/dist/ServerExtensionLoader.d.ts.map +1 -0
- package/dist/ServerExtensionLoader.js +164 -0
- package/dist/ServerExtensionLoader.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +30 -7
package/README.md
CHANGED
|
@@ -1,45 +1,175 @@
|
|
|
1
1
|
# @memberjunction/server-extensions-core
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @memberjunction/server-extensions-core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
78
|
+
### 3. Import Your Extension Package
|
|
17
79
|
|
|
18
|
-
|
|
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
|
-
##
|
|
82
|
+
## API Reference
|
|
21
83
|
|
|
22
|
-
|
|
84
|
+
### `BaseServerExtension`
|
|
23
85
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
Manages the extension lifecycle.
|
|
38
98
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"version": "5.18.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.18.0",
|
|
21
|
+
"@memberjunction/core": "5.18.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
|
}
|