request-scope-api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +275 -0
  2. package/dist/cjs/adapters/adapter.interface.js +9 -0
  3. package/dist/cjs/adapters/adapter.interface.js.map +1 -0
  4. package/dist/cjs/adapters/mongo.adapter.js +188 -0
  5. package/dist/cjs/adapters/mongo.adapter.js.map +1 -0
  6. package/dist/cjs/adapters/mysql.adapter.js +243 -0
  7. package/dist/cjs/adapters/mysql.adapter.js.map +1 -0
  8. package/dist/cjs/adapters/pg.adapter.js +334 -0
  9. package/dist/cjs/adapters/pg.adapter.js.map +1 -0
  10. package/dist/cjs/capture.js +310 -0
  11. package/dist/cjs/capture.js.map +1 -0
  12. package/dist/cjs/config.js +122 -0
  13. package/dist/cjs/config.js.map +1 -0
  14. package/dist/cjs/dashboard/api.js +173 -0
  15. package/dist/cjs/dashboard/api.js.map +1 -0
  16. package/dist/cjs/dashboard/router.js +96 -0
  17. package/dist/cjs/dashboard/router.js.map +1 -0
  18. package/dist/cjs/index.js +49 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/masker.js +73 -0
  21. package/dist/cjs/masker.js.map +1 -0
  22. package/dist/cjs/middleware.js +198 -0
  23. package/dist/cjs/middleware.js.map +1 -0
  24. package/dist/cjs/queue.js +114 -0
  25. package/dist/cjs/queue.js.map +1 -0
  26. package/dist/cjs/retention.js +64 -0
  27. package/dist/cjs/retention.js.map +1 -0
  28. package/dist/cjs/types.js +9 -0
  29. package/dist/cjs/types.js.map +1 -0
  30. package/dist/dashboard/assets/index-C0TqFHk6.css +1 -0
  31. package/dist/dashboard/assets/index-MCuAZo4Q.js +67 -0
  32. package/dist/dashboard/index.html +13 -0
  33. package/dist/esm/adapters/adapter.interface.js +8 -0
  34. package/dist/esm/adapters/adapter.interface.js.map +1 -0
  35. package/dist/esm/adapters/mongo.adapter.js +184 -0
  36. package/dist/esm/adapters/mongo.adapter.js.map +1 -0
  37. package/dist/esm/adapters/mysql.adapter.js +236 -0
  38. package/dist/esm/adapters/mysql.adapter.js.map +1 -0
  39. package/dist/esm/adapters/pg.adapter.js +330 -0
  40. package/dist/esm/adapters/pg.adapter.js.map +1 -0
  41. package/dist/esm/capture.js +304 -0
  42. package/dist/esm/capture.js.map +1 -0
  43. package/dist/esm/config.js +117 -0
  44. package/dist/esm/config.js.map +1 -0
  45. package/dist/esm/dashboard/api.js +168 -0
  46. package/dist/esm/dashboard/api.js.map +1 -0
  47. package/dist/esm/dashboard/router.js +90 -0
  48. package/dist/esm/dashboard/router.js.map +1 -0
  49. package/dist/esm/index.js +50 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/masker.js +70 -0
  52. package/dist/esm/masker.js.map +1 -0
  53. package/dist/esm/middleware.js +193 -0
  54. package/dist/esm/middleware.js.map +1 -0
  55. package/dist/esm/queue.js +110 -0
  56. package/dist/esm/queue.js.map +1 -0
  57. package/dist/esm/retention.js +60 -0
  58. package/dist/esm/retention.js.map +1 -0
  59. package/dist/esm/types.js +8 -0
  60. package/dist/esm/types.js.map +1 -0
  61. package/dist/types/adapters/adapter.interface.d.ts +7 -0
  62. package/dist/types/adapters/mongo.adapter.d.ts +25 -0
  63. package/dist/types/adapters/mysql.adapter.d.ts +24 -0
  64. package/dist/types/adapters/pg.adapter.d.ts +29 -0
  65. package/dist/types/capture.d.ts +88 -0
  66. package/dist/types/config.d.ts +38 -0
  67. package/dist/types/dashboard/api.d.ts +49 -0
  68. package/dist/types/dashboard/router.d.ts +28 -0
  69. package/dist/types/index.d.ts +31 -0
  70. package/dist/types/masker.d.ts +15 -0
  71. package/dist/types/middleware.d.ts +67 -0
  72. package/dist/types/queue.d.ts +49 -0
  73. package/dist/types/retention.d.ts +30 -0
  74. package/dist/types/types.d.ts +101 -0
  75. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # request-scope-api
2
+
3
+ Zero-friction API request observability for Express.js applications.
4
+
5
+ request-scope-api captures HTTP requests and responses, stores them in your database, and provides a dashboard for inspection. It's designed to be production-ready with minimal configuration and zero performance impact on your application.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install request-scope-api
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### MongoDB
16
+
17
+ ```javascript
18
+ import express from 'express';
19
+ import request-scope-api from 'request-scope-api';
20
+
21
+ const app = express();
22
+
23
+ app.use(request-scope-api({
24
+ storage: {
25
+ type: 'mongodb',
26
+ uri: 'mongodb://localhost:27017/myapp'
27
+ }
28
+ }));
29
+
30
+ app.listen(3000);
31
+ ```
32
+
33
+ ### MySQL
34
+
35
+ ```javascript
36
+ import express from 'express';
37
+ import request-scope-api from 'request-scope-api';
38
+
39
+ const app = express();
40
+
41
+ app.use(request-scope-api({
42
+ storage: {
43
+ type: 'mysql',
44
+ host: 'localhost',
45
+ port: 3306,
46
+ database: 'myapp',
47
+ username: 'user',
48
+ password: 'pass'
49
+ }
50
+ }));
51
+
52
+ app.listen(3000);
53
+ ```
54
+
55
+ ### PostgreSQL
56
+
57
+ ```javascript
58
+ import express from 'express';
59
+ import request-scope-api from 'request-scope-api';
60
+
61
+ const app = express();
62
+
63
+ app.use(request-scope-api({
64
+ storage: {
65
+ type: 'postgresql',
66
+ host: 'localhost',
67
+ port: 5432,
68
+ database: 'myapp',
69
+ username: 'user',
70
+ password: 'pass'
71
+ }
72
+ }));
73
+
74
+ app.listen(3000);
75
+ ```
76
+
77
+ ## Dashboard
78
+
79
+ Mount the dashboard at a specific path with optional authentication:
80
+
81
+ ```javascript
82
+ import request-scope-api from 'request-scope-api';
83
+
84
+ const app = express();
85
+
86
+ // Capture middleware
87
+ app.use(request-scope-api({
88
+ storage: {
89
+ type: 'mongodb',
90
+ uri: 'mongodb://localhost:27017/myapp'
91
+ }
92
+ }));
93
+
94
+
95
+ ## Configuration
96
+
97
+ ### request-scope-apiConfig
98
+
99
+ | Option | Type | Default | Description |
100
+ |--------|------|---------|-------------|
101
+ | `storage` | `StorageConfig` | **required** | Database storage configuration |
102
+ | `retentionDays` | `number` | `30` | Number of days to retain records (1-365) |
103
+ | `ignore` | `string[]` | `[]` | URL paths to skip (no capture) |
104
+ | `maskFields` | `string[]` | `[]` | Additional field names to mask (merged with built-in defaults) |
105
+ | `dashboard` | `DashboardConfig` | `undefined` | Dashboard configuration |
106
+
107
+ ### StorageConfig
108
+
109
+ | Option | Type | Required For | Description |
110
+ |--------|------|---------------|-------------|
111
+ | `type` | `'mongodb' \| 'mysql' \| 'postgresql'` | All | Storage backend type |
112
+ | `uri` | `string` | MongoDB | MongoDB connection URI |
113
+ | `host` | `string` | MySQL, PostgreSQL | Database host |
114
+ | `port` | `number` | MySQL, PostgreSQL | Database port |
115
+ | `database` | `string` | MySQL, PostgreSQL | Database name |
116
+ | `username` | `string` | MySQL, PostgreSQL | Database username |
117
+ | `password` | `string` | MySQL, PostgreSQL | Database password |
118
+ | `poolSize` | `number` | All | Connection pool size (default: 5) |
119
+ | `ssl` | `boolean` | All | Enable SSL/TLS (default: false) |
120
+
121
+ ### DashboardConfig
122
+
123
+ | Option | Type | Default | Description |
124
+ |--------|------|---------|-------------|
125
+ | `auth` | `AuthConfig` | `undefined` | Authentication configuration |
126
+ | `mountPath` | `string` | `"/request-scope-api"` | Mount path for the dashboard |
127
+
128
+ ### AuthConfig
129
+
130
+ | Option | Type | Description |
131
+ |--------|------|-------------|
132
+ | `username` | `string` | Basic auth username |
133
+ | `password` | `string` | Basic auth password |
134
+
135
+ ## Database-Specific Setup
136
+
137
+ ### MongoDB
138
+
139
+ - **URI Format**: `mongodb://[username:password@]host[:port][/database][?options]`
140
+ - **Required Fields**: `uri`
141
+ - **Optional**: `poolSize`, `ssl`
142
+
143
+ Example:
144
+ ```javascript
145
+ storage: {
146
+ type: 'mongodb',
147
+ uri: 'mongodb://user:pass@localhost:27017/myapp?authSource=admin'
148
+ }
149
+ ```
150
+
151
+ ### MySQL
152
+
153
+ - **Required Fields**: `host`, `port`, `database`, `username`, `password`
154
+ - **Optional**: `poolSize`, `ssl`
155
+
156
+ Example:
157
+ ```javascript
158
+ storage: {
159
+ type: 'mysql',
160
+ host: 'localhost',
161
+ port: 3306,
162
+ database: 'myapp',
163
+ username: 'user',
164
+ password: 'pass',
165
+ poolSize: 10,
166
+ ssl: true
167
+ }
168
+ ```
169
+
170
+ ### PostgreSQL
171
+
172
+ - **Required Fields**: `host`, `port`, `database`, `username`, `password`
173
+ - **Optional**: `poolSize`, `ssl`
174
+
175
+ Example:
176
+ ```javascript
177
+ storage: {
178
+ type: 'postgresql',
179
+ host: 'localhost',
180
+ port: 5432,
181
+ database: 'myapp',
182
+ username: 'user',
183
+ password: 'pass',
184
+ poolSize: 10,
185
+ ssl: true
186
+ }
187
+ ```
188
+
189
+ ## Features
190
+
191
+ - **Zero Configuration**: Works out of the box with sensible defaults
192
+ - **Multiple Storage Backends**: MongoDB, MySQL, PostgreSQL support
193
+ - **Automatic Retention**: Configurable data retention with automatic cleanup
194
+ - **Sensitive Field Masking**: Built-in masking for passwords, tokens, and API keys
195
+ - **Request/Response Body Capture**: Captures full request and response bodies (with size limits)
196
+ - **Error Tracking**: Captures error messages and stack traces
197
+ - **Performance Metrics**: Records response time and body sizes
198
+ - **Dashboard UI**: Web interface for browsing and filtering requests
199
+ - **Basic Auth**: Optional authentication for dashboard access
200
+
201
+ ## Sensitive Field Masking
202
+
203
+ request-scope-api automatically masks sensitive fields in request headers, request body, and response headers. Built-in sensitive field names (case-insensitive):
204
+
205
+ - `password`
206
+ - `token`
207
+ - `authorization`
208
+ - `apiKey`
209
+ - `secret`
210
+
211
+ Add custom fields to mask via the `maskFields` configuration:
212
+
213
+ ```javascript
214
+ app.use(request-scope-api({
215
+ storage: { /* ... */ },
216
+ maskFields: ['creditCard', 'ssn', 'apiKey']
217
+ }));
218
+ ```
219
+
220
+ ## Ignoring Paths
221
+
222
+ Skip capture for specific URL paths:
223
+
224
+ ```javascript
225
+ app.use(request-scope-api({
226
+ storage: { /* ... */ },
227
+ ignore: ['/health', '/metrics', '/favicon.ico']
228
+ }));
229
+ ```
230
+
231
+ ## Error Handling
232
+
233
+ To capture error data, use the provided error middleware:
234
+
235
+ ```javascript
236
+ import request-scope-api, { errorHandler } from 'request-scope-api';
237
+
238
+ app.use(request-scope-api({ /* ... */ }));
239
+ app.use(errorHandler);
240
+
241
+ // Your routes
242
+ app.get('/api/users', async (req, res, next) => {
243
+ try {
244
+ // ...
245
+ } catch (err) {
246
+ next(err); // Error will be captured
247
+ }
248
+ });
249
+ ```
250
+
251
+ ## API Endpoints
252
+
253
+ The dashboard provides the following API endpoints:
254
+
255
+ - `GET /api/records` - List records with filtering, sorting, and pagination
256
+ - `GET /api/records/:id` - Get a single record by ID
257
+
258
+ ### Query Parameters (GET /api/records)
259
+
260
+ | Parameter | Type | Description |
261
+ |-----------|------|-------------|
262
+ | `search` | `string` | Case-insensitive substring match on URL |
263
+ | `startDate` | `string` | ISO 8601 timestamp (inclusive lower bound) |
264
+ | `endDate` | `string` | ISO 8601 timestamp (inclusive upper bound) |
265
+ | `statusCodeGroup` | `string` | Status code group: `2xx`, `3xx`, `4xx`, `5xx` |
266
+ | `statusCode` | `number` | Exact status code match |
267
+ | `method` | `string` | Exact HTTP method match (case-insensitive) |
268
+ | `sortBy` | `string` | Sort field: `timestamp`, `responseTime`, `statusCode` |
269
+ | `sortOrder` | `string` | Sort order: `asc`, `desc` |
270
+ | `page` | `number` | Page number (default: 1) |
271
+ | `pageSize` | `number` | Page size (default: 25) |
272
+
273
+ ## License
274
+
275
+ MIT
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * StorageAdapter interface — the contract all storage adapters must implement.
4
+ *
5
+ * Adapters should import from this file so they stay decoupled from the
6
+ * top-level types module while still satisfying the shared contract.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=adapter.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.interface.js","sourceRoot":"","sources":["../../../src/adapters/adapter.interface.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * MongoAdapter — MongoDB implementation of the StorageAdapter interface.
4
+ *
5
+ * Collection: `requestscope_records`
6
+ * `_id` is set to the record's UUID string for direct look-ups.
7
+ * A descending index on `timestamp` is created at initialization for sort
8
+ * performance and retention queries.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MongoAdapter = void 0;
12
+ const mongodb_1 = require("mongodb");
13
+ // ---------------------------------------------------------------------------
14
+ // Helper: map statusCodeGroup → {$gte, $lt} range
15
+ // ---------------------------------------------------------------------------
16
+ function statusGroupRange(group) {
17
+ const base = parseInt(group[0], 10) * 100;
18
+ return { $gte: base, $lt: base + 100 };
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // Helper: convert a BSON document back to a RequestRecord
22
+ // ---------------------------------------------------------------------------
23
+ function documentToRecord(doc) {
24
+ return {
25
+ id: doc._id,
26
+ method: doc.method,
27
+ url: doc.url,
28
+ route: doc.route,
29
+ queryParams: doc.queryParams,
30
+ pathParams: doc.pathParams,
31
+ requestHeaders: doc.requestHeaders,
32
+ requestBody: doc.requestBody,
33
+ requestBodySize: doc.requestBodySize,
34
+ clientIp: doc.clientIp,
35
+ userAgent: doc.userAgent,
36
+ statusCode: doc.statusCode,
37
+ responseHeaders: doc.responseHeaders,
38
+ responseBody: doc.responseBody,
39
+ responseBodySize: doc.responseBodySize,
40
+ responseTime: doc.responseTime,
41
+ errorMessage: doc.errorMessage,
42
+ errorStack: doc.errorStack,
43
+ errorStatusCode: doc.errorStatusCode,
44
+ timestamp: doc.timestamp,
45
+ };
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // MongoAdapter
49
+ // ---------------------------------------------------------------------------
50
+ class MongoAdapter {
51
+ constructor(config) {
52
+ this.config = config;
53
+ this.client = null;
54
+ this.collection = null;
55
+ }
56
+ // -------------------------------------------------------------------------
57
+ // initialize()
58
+ // -------------------------------------------------------------------------
59
+ async initialize() {
60
+ const uri = this.config.uri;
61
+ if (!uri) {
62
+ process.stderr.write('[MongoAdapter] storage.uri is required for MongoDB\n');
63
+ return;
64
+ }
65
+ try {
66
+ this.client = new mongodb_1.MongoClient(uri);
67
+ await this.client.connect();
68
+ const db = this.client.db();
69
+ // Create the collection if it does not exist (MongoDB creates it
70
+ // implicitly on first write, but we also create the index here).
71
+ const collections = await db
72
+ .listCollections({ name: 'requestscope_records' }, { nameOnly: true })
73
+ .toArray();
74
+ if (collections.length === 0) {
75
+ await db.createCollection('requestscope_records');
76
+ }
77
+ this.collection = db.collection('requestscope_records');
78
+ // Descending index on timestamp for sort performance and retention queries.
79
+ await this.collection.createIndex({ timestamp: -1 }, { background: true });
80
+ }
81
+ catch (err) {
82
+ process.stderr.write(`[MongoAdapter] Connection error: ${err instanceof Error ? err.message : String(err)}\n`);
83
+ // Do not rethrow — fault-isolated per design.
84
+ }
85
+ }
86
+ // -------------------------------------------------------------------------
87
+ // insert()
88
+ // -------------------------------------------------------------------------
89
+ async insert(records) {
90
+ if (!this.collection || records.length === 0)
91
+ return;
92
+ const docs = records.map((r) => ({
93
+ _id: r.id,
94
+ method: r.method,
95
+ url: r.url,
96
+ route: r.route,
97
+ queryParams: r.queryParams,
98
+ pathParams: r.pathParams,
99
+ requestHeaders: r.requestHeaders,
100
+ requestBody: r.requestBody,
101
+ requestBodySize: r.requestBodySize,
102
+ clientIp: r.clientIp,
103
+ userAgent: r.userAgent,
104
+ statusCode: r.statusCode,
105
+ responseHeaders: r.responseHeaders,
106
+ responseBody: r.responseBody,
107
+ responseBodySize: r.responseBodySize,
108
+ responseTime: r.responseTime,
109
+ errorMessage: r.errorMessage,
110
+ errorStack: r.errorStack,
111
+ errorStatusCode: r.errorStatusCode,
112
+ timestamp: r.timestamp,
113
+ }));
114
+ await this.collection.insertMany(docs, { ordered: false });
115
+ }
116
+ // -------------------------------------------------------------------------
117
+ // query()
118
+ // -------------------------------------------------------------------------
119
+ async query(filters, pagination) {
120
+ if (!this.collection)
121
+ return { records: [], total: 0 };
122
+ // Build MongoDB filter document (AND logic — all conditions applied).
123
+ const mongoFilter = {};
124
+ if (filters.id !== undefined) {
125
+ mongoFilter._id = filters.id;
126
+ }
127
+ if (filters.search !== undefined && filters.search !== '') {
128
+ mongoFilter.url = { $regex: filters.search, $options: 'i' };
129
+ }
130
+ // Date range — lexicographic ISO 8601 comparison is valid for UTC strings.
131
+ if (filters.startDate !== undefined || filters.endDate !== undefined) {
132
+ const tsFilter = {};
133
+ if (filters.startDate !== undefined)
134
+ tsFilter.$gte = filters.startDate;
135
+ if (filters.endDate !== undefined)
136
+ tsFilter.$lte = filters.endDate;
137
+ mongoFilter.timestamp = tsFilter;
138
+ }
139
+ // statusCode (exact match) takes precedence over statusCodeGroup.
140
+ if (filters.statusCode !== undefined) {
141
+ mongoFilter.statusCode = filters.statusCode;
142
+ }
143
+ else if (filters.statusCodeGroup !== undefined) {
144
+ mongoFilter.statusCode = statusGroupRange(filters.statusCodeGroup);
145
+ }
146
+ if (filters.method !== undefined && filters.method !== '') {
147
+ mongoFilter.method = { $regex: filters.method, $options: 'i' };
148
+ }
149
+ // Sort mapping.
150
+ const sortField = filters.sortBy === 'responseTime'
151
+ ? 'responseTime'
152
+ : filters.sortBy === 'statusCode'
153
+ ? 'statusCode'
154
+ : 'timestamp';
155
+ const sortDirection = filters.sortOrder === 'asc' ? 1 : -1;
156
+ // Pagination.
157
+ const { page, pageSize } = pagination;
158
+ const skip = (page - 1) * pageSize;
159
+ // Execute count and find in parallel.
160
+ const [total, docs] = await Promise.all([
161
+ this.collection.countDocuments(mongoFilter),
162
+ this.collection
163
+ .find(mongoFilter)
164
+ .sort({ [sortField]: sortDirection })
165
+ .skip(skip)
166
+ .limit(pageSize)
167
+ .toArray(),
168
+ ]);
169
+ return {
170
+ records: docs.map(documentToRecord),
171
+ total,
172
+ };
173
+ }
174
+ // -------------------------------------------------------------------------
175
+ // deleteOlderThan()
176
+ // -------------------------------------------------------------------------
177
+ async deleteOlderThan(date) {
178
+ if (!this.collection)
179
+ return 0;
180
+ const cutoff = date.toISOString();
181
+ const result = await this.collection.deleteMany({
182
+ timestamp: { $lt: cutoff },
183
+ });
184
+ return result.deletedCount;
185
+ }
186
+ }
187
+ exports.MongoAdapter = MongoAdapter;
188
+ //# sourceMappingURL=mongo.adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo.adapter.js","sourceRoot":"","sources":["../../../src/adapters/mongo.adapter.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,qCAAoE;AA8BpE,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,SAAS,gBAAgB,CAAC,KAAoC;IAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC;IAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,CAAC;AACzC,CAAC;AAED,8EAA8E;AAC9E,0DAA0D;AAC1D,8EAA8E;AAE9E,SAAS,gBAAgB,CAAC,GAAmB;IAC3C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,GAAG;QACX,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;QACtC,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,MAAa,YAAY;IAIvB,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QAH1C,WAAM,GAAuB,IAAI,CAAC;QAClC,eAAU,GAAsC,IAAI,CAAC;IAER,CAAC;IAEtD,4EAA4E;IAC5E,eAAe;IACf,4EAA4E;IAE5E,KAAK,CAAC,UAAU;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;QAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,GAAG,IAAI,qBAAW,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAE5B,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YAE5B,iEAAiE;YACjE,iEAAiE;YACjE,MAAM,WAAW,GAAG,MAAM,EAAE;iBACzB,eAAe,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;iBACrE,OAAO,EAAE,CAAC;YAEb,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAM,EAAE,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAiB,sBAAsB,CAAC,CAAC;YAExE,4EAA4E;YAC5E,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oCAAoC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACzF,CAAC;YACF,8CAA8C;QAChD,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,WAAW;IACX,4EAA4E;IAE5E,KAAK,CAAC,MAAM,CAAC,OAAwB;QACnC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErD,MAAM,IAAI,GAAqB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjD,GAAG,EAAE,CAAC,CAAC,EAAE;YACT,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;YACpC,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC,CAAC;QAEJ,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,4EAA4E;IAC5E,UAAU;IACV,4EAA4E;IAE5E,KAAK,CAAC,KAAK,CACT,OAAqB,EACrB,UAA8C;QAE9C,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAEvD,sEAAsE;QACtE,MAAM,WAAW,GAA2B,EAAE,CAAC;QAE/C,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC7B,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC;QAC/B,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC1D,WAAW,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC9D,CAAC;QAED,2EAA2E;QAC3E,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACrE,MAAM,QAAQ,GAAqC,EAAE,CAAC;YACtD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;gBAAE,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC;YACvE,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;gBAAE,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;YACnE,WAAW,CAAC,SAAS,GAAG,QAAQ,CAAC;QACnC,CAAC;QAED,kEAAkE;QAClE,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACrC,WAAW,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAC9C,CAAC;aAAM,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACjD,WAAW,CAAC,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC1D,WAAW,CAAC,MAAM,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QACjE,CAAC;QAED,gBAAgB;QAChB,MAAM,SAAS,GACb,OAAO,CAAC,MAAM,KAAK,cAAc;YAC/B,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY;gBAC/B,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,WAAW,CAAC;QACpB,MAAM,aAAa,GAAW,OAAO,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnE,cAAc;QACd,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC;QACtC,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QAEnC,sCAAsC;QACtC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACtC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,WAAW,CAAC;YAC3C,IAAI,CAAC,UAAU;iBACZ,IAAI,CAAC,WAAW,CAAC;iBACjB,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;iBACpC,IAAI,CAAC,IAAI,CAAC;iBACV,KAAK,CAAC,QAAQ,CAAC;iBACf,OAAO,EAAE;SACb,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACnC,KAAK;SACN,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,oBAAoB;IACpB,4EAA4E;IAE5E,KAAK,CAAC,eAAe,CAAC,IAAU;QAC9B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAC9C,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;SAC3B,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;CACF;AAlKD,oCAkKC"}