@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 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)
@@ -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"}