@memberjunction/component-registry-server 2.99.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 +490 -0
- package/dist/Server.d.ts +194 -0
- package/dist/Server.d.ts.map +1 -0
- package/dist/Server.js +450 -0
- package/dist/Server.js.map +1 -0
- package/dist/config.d.ts +145 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +53 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# @memberjunction/component-registry-server
|
|
2
|
+
|
|
3
|
+
MemberJunction Component Registry Server - A REST API server for serving MemberJunction interactive components.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Component Registry Server provides a standardized REST API for discovering and retrieving MemberJunction interactive components. It implements the Component Registry API v1 specification, allowing any MemberJunction system to connect and consume components dynamically.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **REST API v1**: Standardized endpoints for component discovery and retrieval
|
|
12
|
+
- **Extensible Architecture**: Base class design allows easy customization via inheritance
|
|
13
|
+
- **Authentication Support**: Override methods to implement custom authentication
|
|
14
|
+
- **Database Integration**: Uses MemberJunction's entity system for component storage
|
|
15
|
+
- **CORS Support**: Configurable cross-origin resource sharing
|
|
16
|
+
- **Component Versioning**: Automatic selection of latest component versions
|
|
17
|
+
- **Search Capabilities**: Full-text search across component names, titles, and descriptions
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @memberjunction/component-registry-server
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Configure the Server
|
|
28
|
+
|
|
29
|
+
Add configuration to your `mj.config.cjs` file in the project root:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
module.exports = {
|
|
33
|
+
// Database configuration
|
|
34
|
+
dbHost: 'localhost',
|
|
35
|
+
dbDatabase: 'MemberJunction',
|
|
36
|
+
dbUsername: 'your-username',
|
|
37
|
+
dbPassword: 'your-password',
|
|
38
|
+
mjCoreSchema: '__mj',
|
|
39
|
+
|
|
40
|
+
// Component Registry configuration
|
|
41
|
+
componentRegistrySettings: {
|
|
42
|
+
port: 3200, // Port to run the server on
|
|
43
|
+
enableRegistry: true, // Enable the registry server
|
|
44
|
+
registryId: null, // Optional: GUID of registry record
|
|
45
|
+
requireAuth: false, // Whether to require authentication
|
|
46
|
+
corsOrigins: ['*'] // Allowed CORS origins
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Run the Server
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Using npm scripts
|
|
55
|
+
npm start
|
|
56
|
+
|
|
57
|
+
# Or directly via node
|
|
58
|
+
node dist/index.js
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The server will start on the configured port (default: 3200) and be available at:
|
|
62
|
+
```
|
|
63
|
+
http://localhost:3200/api/v1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Endpoints
|
|
67
|
+
|
|
68
|
+
### Registry Information
|
|
69
|
+
|
|
70
|
+
**GET /api/v1/registry**
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"name": "Local Component Registry",
|
|
74
|
+
"description": "MemberJunction Component Registry",
|
|
75
|
+
"version": "v1",
|
|
76
|
+
"requiresAuth": false
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Health Check
|
|
81
|
+
|
|
82
|
+
**GET /api/v1/health**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"status": "healthy",
|
|
86
|
+
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
87
|
+
"version": "v1",
|
|
88
|
+
"componentCount": 42
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### List Components
|
|
93
|
+
|
|
94
|
+
**GET /api/v1/components**
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"components": [
|
|
98
|
+
{
|
|
99
|
+
"namespace": "@memberjunction/dashboards",
|
|
100
|
+
"name": "revenue-tracker",
|
|
101
|
+
"version": "1.0.0",
|
|
102
|
+
"title": "Revenue Tracker Dashboard",
|
|
103
|
+
"description": "Track revenue metrics over time",
|
|
104
|
+
"type": "Dashboard",
|
|
105
|
+
"status": "Published"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"total": 1
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Search Components
|
|
113
|
+
|
|
114
|
+
**GET /api/v1/components/search?q=revenue&type=Dashboard**
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"results": [...],
|
|
118
|
+
"total": 5,
|
|
119
|
+
"query": "revenue"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Get Component
|
|
124
|
+
|
|
125
|
+
**GET /api/v1/components/:namespace/:name**
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
129
|
+
"namespace": "@memberjunction/dashboards",
|
|
130
|
+
"name": "revenue-tracker",
|
|
131
|
+
"version": "1.0.0",
|
|
132
|
+
"specification": {
|
|
133
|
+
"name": "revenue-tracker",
|
|
134
|
+
"title": "Revenue Tracker Dashboard",
|
|
135
|
+
"code": "// Component code here",
|
|
136
|
+
"props": [...],
|
|
137
|
+
"dataRequirements": [...]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Optional version parameter:
|
|
143
|
+
**GET /api/v1/components/:namespace/:name?version=1.0.0**
|
|
144
|
+
|
|
145
|
+
## Extending the Server
|
|
146
|
+
|
|
147
|
+
The Component Registry Server is designed to be easily extended through inheritance. The base `ComponentRegistryAPIServer` class provides virtual methods that can be overridden to customize behavior.
|
|
148
|
+
|
|
149
|
+
### Custom Authentication
|
|
150
|
+
|
|
151
|
+
Create a subclass and override the `checkAPIKey` method:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { ComponentRegistryAPIServer } from '@memberjunction/component-registry-server';
|
|
155
|
+
import { Request } from 'express';
|
|
156
|
+
|
|
157
|
+
class MyAuthenticatedRegistryServer extends ComponentRegistryAPIServer {
|
|
158
|
+
/**
|
|
159
|
+
* Implement custom authentication logic
|
|
160
|
+
*/
|
|
161
|
+
protected async checkAPIKey(req: Request): Promise<boolean> {
|
|
162
|
+
// Check for API key in header
|
|
163
|
+
const apiKey = req.headers['x-api-key'] as string;
|
|
164
|
+
if (!apiKey) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate against your auth provider
|
|
169
|
+
return await this.validateWithAuthProvider(apiKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async validateWithAuthProvider(apiKey: string): Promise<boolean> {
|
|
173
|
+
// Your custom validation logic here
|
|
174
|
+
// Could check database, external service, JWT, etc.
|
|
175
|
+
return apiKey === process.env.VALID_API_KEY;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Start your custom server
|
|
180
|
+
const server = new MyAuthenticatedRegistryServer();
|
|
181
|
+
await server.initialize();
|
|
182
|
+
await server.start();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Custom Component Filtering
|
|
186
|
+
|
|
187
|
+
Override the `getComponentFilter` method to control which components are served:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
class FilteredRegistryServer extends ComponentRegistryAPIServer {
|
|
191
|
+
/**
|
|
192
|
+
* Only serve components from specific registries
|
|
193
|
+
*/
|
|
194
|
+
protected getComponentFilter(): string {
|
|
195
|
+
// Serve local components and components from a specific registry
|
|
196
|
+
return "(SourceRegistryID IS NULL OR SourceRegistryID = 'abc-123-def')";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Using Different Database Tables
|
|
202
|
+
|
|
203
|
+
Override the route handlers to pull components from different tables or entities:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
class CustomTableRegistryServer extends ComponentRegistryAPIServer {
|
|
207
|
+
/**
|
|
208
|
+
* Use a different entity/table for components
|
|
209
|
+
* For example, using a "Custom Components" entity instead of "MJ: Components"
|
|
210
|
+
*/
|
|
211
|
+
protected async listComponents(req: Request, res: Response): Promise<void> {
|
|
212
|
+
try {
|
|
213
|
+
const rv = new RunView();
|
|
214
|
+
const result = await rv.RunView({
|
|
215
|
+
EntityName: 'Custom Components', // Your custom entity name
|
|
216
|
+
ExtraFilter: "IsActive = 1 AND IsPublished = 1",
|
|
217
|
+
OrderBy: 'CategoryName, ComponentName, Version DESC',
|
|
218
|
+
ResultType: 'entity_object'
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!result.Success) {
|
|
222
|
+
res.status(500).json({ error: result.ErrorMessage });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Map your custom fields to the API response format
|
|
227
|
+
const components = (result.Results || []).map(c => ({
|
|
228
|
+
namespace: c.CategoryName, // Map your fields
|
|
229
|
+
name: c.ComponentName, // to the expected
|
|
230
|
+
version: c.Version, // API format
|
|
231
|
+
title: c.DisplayName,
|
|
232
|
+
description: c.Summary,
|
|
233
|
+
type: c.ComponentType,
|
|
234
|
+
status: c.PublishStatus
|
|
235
|
+
}));
|
|
236
|
+
|
|
237
|
+
res.json({ components, total: components.length });
|
|
238
|
+
} catch (error) {
|
|
239
|
+
res.status(500).json({ error: 'Failed to list components' });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
protected async getComponent(req: Request, res: Response): Promise<void> {
|
|
244
|
+
const { namespace, name } = req.params;
|
|
245
|
+
|
|
246
|
+
const rv = new RunView();
|
|
247
|
+
const result = await rv.RunView({
|
|
248
|
+
EntityName: 'Custom Components',
|
|
249
|
+
ExtraFilter: `CategoryName = '${namespace}' AND ComponentName = '${name}'`,
|
|
250
|
+
OrderBy: 'Version DESC',
|
|
251
|
+
MaxRows: 1,
|
|
252
|
+
ResultType: 'entity_object'
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (!result.Success || !result.Results?.length) {
|
|
256
|
+
res.status(404).json({ error: 'Component not found' });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const component = result.Results[0];
|
|
261
|
+
res.json({
|
|
262
|
+
id: component.ID,
|
|
263
|
+
namespace: component.CategoryName,
|
|
264
|
+
name: component.ComponentName,
|
|
265
|
+
version: component.Version,
|
|
266
|
+
specification: JSON.parse(component.ComponentSpec) // Your spec field
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Override search to use your custom table
|
|
271
|
+
protected async searchComponents(req: Request, res: Response): Promise<void> {
|
|
272
|
+
const { q, type } = req.query;
|
|
273
|
+
|
|
274
|
+
let filter = "IsActive = 1 AND IsPublished = 1";
|
|
275
|
+
if (q) {
|
|
276
|
+
filter += ` AND (ComponentName LIKE '%${q}%' OR DisplayName LIKE '%${q}%')`;
|
|
277
|
+
}
|
|
278
|
+
if (type) {
|
|
279
|
+
filter += ` AND ComponentType = '${type}'`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ... rest of implementation
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Custom Routes and Middleware
|
|
288
|
+
|
|
289
|
+
Add additional routes or modify middleware:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
class ExtendedRegistryServer extends ComponentRegistryAPIServer {
|
|
293
|
+
/**
|
|
294
|
+
* Add custom middleware
|
|
295
|
+
*/
|
|
296
|
+
protected setupMiddleware(): void {
|
|
297
|
+
// Call parent middleware setup
|
|
298
|
+
super.setupMiddleware();
|
|
299
|
+
|
|
300
|
+
// Add custom middleware
|
|
301
|
+
this.app.use('/api/v1/admin', this.adminAuthMiddleware);
|
|
302
|
+
|
|
303
|
+
// Add request logging
|
|
304
|
+
this.app.use((req, res, next) => {
|
|
305
|
+
console.log(`${req.method} ${req.path}`);
|
|
306
|
+
next();
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Add custom routes
|
|
312
|
+
*/
|
|
313
|
+
protected setupRoutes(): void {
|
|
314
|
+
// Call parent route setup
|
|
315
|
+
super.setupRoutes();
|
|
316
|
+
|
|
317
|
+
// Add admin routes
|
|
318
|
+
this.app.post('/api/v1/admin/components', this.createComponent.bind(this));
|
|
319
|
+
this.app.delete('/api/v1/admin/components/:id', this.deleteComponent.bind(this));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async createComponent(req: Request, res: Response) {
|
|
323
|
+
// Custom component creation logic
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private async deleteComponent(req: Request, res: Response) {
|
|
327
|
+
// Custom component deletion logic
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Database Provider Customization
|
|
333
|
+
|
|
334
|
+
Override the database setup for different providers:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
class PostgresRegistryServer extends ComponentRegistryAPIServer {
|
|
338
|
+
/**
|
|
339
|
+
* Use PostgreSQL instead of SQL Server
|
|
340
|
+
*/
|
|
341
|
+
protected async setupDatabase(): Promise<void> {
|
|
342
|
+
// Your PostgreSQL setup logic
|
|
343
|
+
const pgClient = new PostgreSQLClient(config);
|
|
344
|
+
await pgClient.connect();
|
|
345
|
+
|
|
346
|
+
// Setup MemberJunction with PostgreSQL provider
|
|
347
|
+
await setupPostgreSQLClient(pgClient);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Advanced Usage
|
|
353
|
+
|
|
354
|
+
### Programmatic Usage
|
|
355
|
+
|
|
356
|
+
Use the server as a library in your application:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { ComponentRegistryAPIServer } from '@memberjunction/component-registry-server';
|
|
360
|
+
|
|
361
|
+
async function startMyApp() {
|
|
362
|
+
// Create and configure server
|
|
363
|
+
const registryServer = new ComponentRegistryAPIServer();
|
|
364
|
+
|
|
365
|
+
// Initialize without starting
|
|
366
|
+
await registryServer.initialize();
|
|
367
|
+
|
|
368
|
+
// Access the Express app for integration
|
|
369
|
+
const app = registryServer.app;
|
|
370
|
+
|
|
371
|
+
// Add to existing Express app
|
|
372
|
+
existingApp.use('/registry', app);
|
|
373
|
+
|
|
374
|
+
// Or start standalone
|
|
375
|
+
await registryServer.start();
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Multiple Registry Support
|
|
380
|
+
|
|
381
|
+
Run multiple registries on different ports:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
class MultiRegistryManager {
|
|
385
|
+
private registries: Map<string, ComponentRegistryAPIServer> = new Map();
|
|
386
|
+
|
|
387
|
+
async startRegistry(name: string, port: number, registryId: string) {
|
|
388
|
+
const server = new ComponentRegistryAPIServer();
|
|
389
|
+
|
|
390
|
+
// Override configuration
|
|
391
|
+
componentRegistrySettings.port = port;
|
|
392
|
+
componentRegistrySettings.registryId = registryId;
|
|
393
|
+
|
|
394
|
+
await server.initialize();
|
|
395
|
+
await server.start();
|
|
396
|
+
|
|
397
|
+
this.registries.set(name, server);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async startAll() {
|
|
401
|
+
await this.startRegistry('public', 3200, 'public-registry-id');
|
|
402
|
+
await this.startRegistry('private', 3201, 'private-registry-id');
|
|
403
|
+
await this.startRegistry('internal', 3202, 'internal-registry-id');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Configuration Reference
|
|
409
|
+
|
|
410
|
+
### componentRegistrySettings
|
|
411
|
+
|
|
412
|
+
| Property | Type | Default | Description |
|
|
413
|
+
|----------|------|---------|-------------|
|
|
414
|
+
| `port` | number | 3200 | Port number for the server |
|
|
415
|
+
| `enableRegistry` | boolean | false | Whether to enable the registry server |
|
|
416
|
+
| `registryId` | string \| null | null | Optional GUID of the registry record in MJ: Component Registries |
|
|
417
|
+
| `requireAuth` | boolean | false | Whether to require authentication for component endpoints |
|
|
418
|
+
| `corsOrigins` | string[] | ['*'] | Allowed CORS origins |
|
|
419
|
+
|
|
420
|
+
## Database Schema
|
|
421
|
+
|
|
422
|
+
The Component Registry Server uses the following MemberJunction entities:
|
|
423
|
+
|
|
424
|
+
- **MJ: Component Registries**: Registry metadata and configuration
|
|
425
|
+
- **MJ: Components**: Component definitions and specifications
|
|
426
|
+
- **MJ: Component Libraries**: External library dependencies
|
|
427
|
+
- **MJ: Component Dependencies**: Component-to-component dependencies
|
|
428
|
+
|
|
429
|
+
## Security Considerations
|
|
430
|
+
|
|
431
|
+
### Authentication
|
|
432
|
+
|
|
433
|
+
By default, the server runs without authentication. For production use:
|
|
434
|
+
|
|
435
|
+
1. Set `requireAuth: true` in configuration
|
|
436
|
+
2. Extend the server class and override `checkAPIKey()`
|
|
437
|
+
3. Implement your authentication strategy (Bearer tokens, API keys, JWT, etc.)
|
|
438
|
+
|
|
439
|
+
### CORS
|
|
440
|
+
|
|
441
|
+
Configure CORS origins appropriately for your environment:
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
componentRegistrySettings: {
|
|
445
|
+
corsOrigins: [
|
|
446
|
+
'https://app.example.com',
|
|
447
|
+
'https://admin.example.com'
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### SQL Injection
|
|
453
|
+
|
|
454
|
+
The server uses parameterized queries via MemberJunction's RunView system. User input is escaped when building filters.
|
|
455
|
+
|
|
456
|
+
## Troubleshooting
|
|
457
|
+
|
|
458
|
+
### Server Won't Start
|
|
459
|
+
|
|
460
|
+
1. Check database connection settings in `mj.config.cjs`
|
|
461
|
+
2. Ensure the database is accessible
|
|
462
|
+
3. Verify `enableRegistry` is set to `true`
|
|
463
|
+
4. Check port availability
|
|
464
|
+
|
|
465
|
+
### Components Not Found
|
|
466
|
+
|
|
467
|
+
1. Verify components exist in the database
|
|
468
|
+
2. Check component `Status` is 'Published'
|
|
469
|
+
3. Ensure `SourceRegistryID` is NULL for local components
|
|
470
|
+
4. Review the component filter if using a custom implementation
|
|
471
|
+
|
|
472
|
+
### Authentication Issues
|
|
473
|
+
|
|
474
|
+
1. Verify `requireAuth` setting matches your expectations
|
|
475
|
+
2. Check your `checkAPIKey` implementation
|
|
476
|
+
3. Ensure auth headers are being sent correctly
|
|
477
|
+
|
|
478
|
+
## Contributing
|
|
479
|
+
|
|
480
|
+
Contributions are welcome! Please follow the MemberJunction contribution guidelines.
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
ISC License - See LICENSE file for details
|
|
485
|
+
|
|
486
|
+
## Support
|
|
487
|
+
|
|
488
|
+
For issues and questions:
|
|
489
|
+
- GitHub Issues: [MemberJunction/MJ](https://github.com/MemberJunction/MJ/issues)
|
|
490
|
+
- Documentation: [docs.memberjunction.org](https://docs.memberjunction.org)
|
package/dist/Server.d.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { Metadata } from '@memberjunction/core';
|
|
3
|
+
import { ComponentEntity, ComponentRegistryEntity } from '@memberjunction/core-entities';
|
|
4
|
+
import * as sql from 'mssql';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for the Component Registry API Server.
|
|
7
|
+
* This class provides a complete implementation of the Component Registry API v1 specification.
|
|
8
|
+
*
|
|
9
|
+
* To customize the server behavior, extend this class and override the appropriate methods.
|
|
10
|
+
* Common customization points include:
|
|
11
|
+
* - Authentication: Override `checkAPIKey()` to implement custom authentication
|
|
12
|
+
* - Component filtering: Override `getComponentFilter()` to customize which components are served
|
|
13
|
+
* - Response formatting: Override the route handler methods to customize response formats
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* class MyCustomRegistryServer extends ComponentRegistryAPIServer {
|
|
18
|
+
* protected async checkAPIKey(req: Request): Promise<boolean> {
|
|
19
|
+
* const apiKey = req.headers['x-api-key'];
|
|
20
|
+
* return await myCustomAuthProvider.validateKey(apiKey);
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class ComponentRegistryAPIServer {
|
|
26
|
+
protected app: express.Application;
|
|
27
|
+
protected registry: ComponentRegistryEntity | null;
|
|
28
|
+
protected metadata: Metadata;
|
|
29
|
+
protected pool: sql.ConnectionPool | null;
|
|
30
|
+
constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the server, including database connection, middleware, and routes.
|
|
33
|
+
* This method should be called before starting the server.
|
|
34
|
+
*
|
|
35
|
+
* @returns Promise that resolves when initialization is complete
|
|
36
|
+
* @throws Error if database connection fails or registry cannot be loaded
|
|
37
|
+
*/
|
|
38
|
+
initialize(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Start the Express server on the configured port.
|
|
41
|
+
* Must be called after `initialize()`.
|
|
42
|
+
*
|
|
43
|
+
* @returns Promise that resolves when the server is listening
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Set up the database connection using MemberJunction's SQL Server provider.
|
|
48
|
+
* Override this method to use a different database provider or connection strategy.
|
|
49
|
+
*
|
|
50
|
+
* @protected
|
|
51
|
+
* @virtual
|
|
52
|
+
*/
|
|
53
|
+
protected setupDatabase(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Load the registry metadata from the database.
|
|
56
|
+
* This is called automatically if a registryId is provided in the configuration.
|
|
57
|
+
*
|
|
58
|
+
* @protected
|
|
59
|
+
* @virtual
|
|
60
|
+
*/
|
|
61
|
+
protected loadRegistry(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Set up Express middleware.
|
|
64
|
+
* Override this method to add custom middleware or modify the middleware stack.
|
|
65
|
+
*
|
|
66
|
+
* @protected
|
|
67
|
+
* @virtual
|
|
68
|
+
*/
|
|
69
|
+
protected setupMiddleware(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Authentication middleware that calls the checkAPIKey method.
|
|
72
|
+
* This middleware is automatically applied to /api/v1/components routes when requireAuth is true.
|
|
73
|
+
*
|
|
74
|
+
* @protected
|
|
75
|
+
*/
|
|
76
|
+
protected authMiddleware(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Check if the API key in the request is valid.
|
|
79
|
+
* By default, this method always returns true (no authentication).
|
|
80
|
+
*
|
|
81
|
+
* Override this method to implement custom authentication logic.
|
|
82
|
+
* Common patterns include:
|
|
83
|
+
* - Checking Bearer tokens in Authorization header
|
|
84
|
+
* - Validating API keys in custom headers
|
|
85
|
+
* - Verifying JWT tokens
|
|
86
|
+
* - Checking against a database of valid keys
|
|
87
|
+
*
|
|
88
|
+
* @param req - The Express request object
|
|
89
|
+
* @returns Promise<boolean> - True if the request is authenticated, false otherwise
|
|
90
|
+
*
|
|
91
|
+
* @protected
|
|
92
|
+
* @virtual
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* protected async checkAPIKey(req: Request): Promise<boolean> {
|
|
97
|
+
* const apiKey = req.headers['x-api-key'] as string;
|
|
98
|
+
* if (!apiKey) return false;
|
|
99
|
+
*
|
|
100
|
+
* // Check against database, cache, or external service
|
|
101
|
+
* return await this.validateKeyInDatabase(apiKey);
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
protected checkAPIKey(req: Request): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* Set up the API routes.
|
|
108
|
+
* Override this method to add custom routes or modify existing ones.
|
|
109
|
+
*
|
|
110
|
+
* @protected
|
|
111
|
+
* @virtual
|
|
112
|
+
*/
|
|
113
|
+
protected setupRoutes(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get the base filter for component queries.
|
|
116
|
+
* By default, this returns components where SourceRegistryID IS NULL (local components only).
|
|
117
|
+
*
|
|
118
|
+
* Override this method to customize which components are served by the registry.
|
|
119
|
+
*
|
|
120
|
+
* @returns The SQL filter string to apply to all component queries
|
|
121
|
+
*
|
|
122
|
+
* @protected
|
|
123
|
+
* @virtual
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* protected getComponentFilter(): string {
|
|
128
|
+
* // Include both local and specific external registry components
|
|
129
|
+
* return "(SourceRegistryID IS NULL OR SourceRegistryID = 'abc-123')";
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
protected getComponentFilter(): string;
|
|
134
|
+
/**
|
|
135
|
+
* Handler for GET /api/v1/registry
|
|
136
|
+
* Returns basic information about the registry.
|
|
137
|
+
*
|
|
138
|
+
* @protected
|
|
139
|
+
* @virtual
|
|
140
|
+
*/
|
|
141
|
+
protected getRegistryInfo(req: Request, res: Response): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Handler for GET /api/v1/health
|
|
144
|
+
* Returns the health status of the registry server.
|
|
145
|
+
*
|
|
146
|
+
* @protected
|
|
147
|
+
* @virtual
|
|
148
|
+
*/
|
|
149
|
+
protected getHealth(req: Request, res: Response): Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Handler for GET /api/v1/components
|
|
152
|
+
* Lists all published components in the registry, showing only the latest version of each.
|
|
153
|
+
*
|
|
154
|
+
* @protected
|
|
155
|
+
* @virtual
|
|
156
|
+
*/
|
|
157
|
+
protected listComponents(req: Request, res: Response): Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Handler for GET /api/v1/components/search
|
|
160
|
+
* Search for components by query string and optional type filter.
|
|
161
|
+
*
|
|
162
|
+
* @protected
|
|
163
|
+
* @virtual
|
|
164
|
+
*/
|
|
165
|
+
protected searchComponents(req: Request, res: Response): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Handler for GET /api/v1/components/:namespace/:name
|
|
168
|
+
* Get a specific component by namespace and name.
|
|
169
|
+
* Optionally specify a version with ?version=x.x.x query parameter.
|
|
170
|
+
*
|
|
171
|
+
* @protected
|
|
172
|
+
* @virtual
|
|
173
|
+
*/
|
|
174
|
+
protected getComponent(req: Request, res: Response): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Helper method to get the latest version of each component from a list.
|
|
177
|
+
* Components are grouped by namespace/name and the highest version is selected.
|
|
178
|
+
*
|
|
179
|
+
* @param components - Array of components potentially containing multiple versions
|
|
180
|
+
* @returns Array of components with only the latest version of each
|
|
181
|
+
*
|
|
182
|
+
* @protected
|
|
183
|
+
*/
|
|
184
|
+
protected getLatestVersions(components: ComponentEntity[]): ComponentEntity[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Start the Component Registry Server using the default implementation.
|
|
188
|
+
* This function checks if the registry is enabled in configuration before starting.
|
|
189
|
+
*
|
|
190
|
+
* @returns Promise that resolves when the server is running
|
|
191
|
+
* @throws Error if initialization or startup fails
|
|
192
|
+
*/
|
|
193
|
+
export declare function startComponentRegistryServer(): Promise<void>;
|
|
194
|
+
//# sourceMappingURL=Server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Server.d.ts","sourceRoot":"","sources":["../src/Server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnE,OAAO,EACL,QAAQ,EAIT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAEzF,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAG7B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,0BAA0B;IACrC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC;IACnC,SAAS,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAAQ;IAC1D,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC7B,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,cAAc,GAAG,IAAI,CAAQ;;IAOjD;;;;;;OAMG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxC;;;;;OAKG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYnC;;;;;;OAMG;cACa,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B9C;;;;;;OAMG;cACa,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7C;;;;;;OAMG;IACH,SAAS,CAAC,eAAe,IAAI,IAAI;IAejC;;;;;OAKG;cACa,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAc9F;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;cACa,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3D;;;;;;OAMG;IACH,SAAS,CAAC,WAAW,IAAI,IAAI;IAW7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,kBAAkB,IAAI,MAAM;IAItC;;;;;;OAMG;cACa,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3E;;;;;;OAMG;cACa,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBrE;;;;;;OAMG;cACa,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC1E;;;;;;OAMG;cACa,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqD5E;;;;;;;OAOG;cACa,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CxE;;;;;;;;OAQG;IACH,SAAS,CAAC,iBAAiB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE;CAY9E;AAED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CASlE"}
|