90dc-core 1.10.20 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,361 @@
1
1
  # 90dc-core
2
- A package that contains utils and interfaces used to create 90dc
2
+
3
+ Shared utilities, models, and middleware for 90 Day Challenge microservices.
4
+
5
+ ## Version 1.11.0 - NEW Features ✨
6
+
7
+ This version adds **configuration validation** and **error handling middleware** to improve code quality across all services.
8
+
9
+ ### What's New
10
+
11
+ - ✅ **Type-safe configuration** with Zod validation
12
+ - ✅ **Consistent error handling** across all services
13
+ - ✅ **Request validation middleware** with Zod
14
+ - ✅ **Comprehensive error classes** (404, 400, 401, 403, 500, etc.)
15
+ - ✅ **Fail-fast validation** on startup
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install 90dc-core@latest zod
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Configuration Validation
30
+
31
+ ```typescript
32
+ import { BaseConfigSchema, ConfigValidator } from '90dc-core';
33
+ import { z } from 'zod';
34
+
35
+ // Extend base config with service-specific settings
36
+ const ConfigSchema = BaseConfigSchema.extend({
37
+ GOOGLE_CLIENT_ID: z.string().min(1),
38
+ APPLE_TEAM_ID: z.string().min(1),
39
+ MOLLIE_API_KEY: z.string().min(1)
40
+ });
41
+
42
+ // Create validated config (fails fast on startup if invalid)
43
+ const config = new ConfigValidator(ConfigSchema);
44
+
45
+ // Use with type safety
46
+ const port = config.get('PORT'); // Type: number
47
+ const jwtSecret = config.get('JWT_SECRET'); // Type: string
48
+ const googleClientId = config.get('GOOGLE_CLIENT_ID'); // Type: string
49
+ ```
50
+
51
+ ### 2. Error Handling Middleware
52
+
53
+ ```typescript
54
+ import Koa from 'koa';
55
+ import { createErrorMiddleware } from '90dc-core';
56
+
57
+ const app = new Koa();
58
+
59
+ // Add error middleware FIRST
60
+ app.use(createErrorMiddleware({
61
+ exposeErrorDetails: config.isDevelopment()
62
+ }));
63
+
64
+ // Add your routes
65
+ app.use(router.routes());
66
+ ```
67
+
68
+ ### 3. Throwing Errors
69
+
70
+ ```typescript
71
+ import {
72
+ NotFoundError,
73
+ ValidationError,
74
+ ForbiddenError,
75
+ AuthenticationError
76
+ } from '90dc-core';
77
+
78
+ class UserController {
79
+ async getUser(ctx: Context) {
80
+ const user = await User.findByPk(ctx.params.id);
81
+
82
+ if (!user) {
83
+ throw new NotFoundError('User'); // Returns 404 with consistent format
84
+ }
85
+
86
+ ctx.body = { user };
87
+ }
88
+
89
+ async createUser(ctx: Context) {
90
+ const existing = await User.findOne({ where: { email } });
91
+
92
+ if (existing) {
93
+ throw new ConflictError('Email already registered'); // Returns 409
94
+ }
95
+
96
+ // ... create user
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### 4. Request Validation
102
+
103
+ ```typescript
104
+ import { validateRequest } from '90dc-core';
105
+ import { z } from 'zod';
106
+
107
+ const CreateUserSchema = z.object({
108
+ email: z.string().email(),
109
+ password: z.string().min(8),
110
+ firstName: z.string().min(1).max(100)
111
+ });
112
+
113
+ router.post('/users',
114
+ validateRequest({ body: CreateUserSchema }),
115
+ async (ctx) => {
116
+ // ctx.request.body is now validated and typed
117
+ const user = await User.create(ctx.request.body);
118
+ ctx.body = { user };
119
+ }
120
+ );
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Error Response Format
126
+
127
+ All errors return consistent JSON:
128
+
129
+ ```json
130
+ {
131
+ "success": false,
132
+ "error": {
133
+ "code": "NOT_FOUND",
134
+ "message": "User not found"
135
+ }
136
+ }
137
+ ```
138
+
139
+ With details for validation errors:
140
+
141
+ ```json
142
+ {
143
+ "success": false,
144
+ "error": {
145
+ "code": "VALIDATION_ERROR",
146
+ "message": "Request validation failed",
147
+ "details": {
148
+ "validationErrors": [
149
+ {
150
+ "path": "email",
151
+ "message": "Invalid email",
152
+ "code": "invalid_string"
153
+ }
154
+ ]
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Available Error Types
163
+
164
+ ```typescript
165
+ import {
166
+ ValidationError, // 400 - Invalid data
167
+ AuthenticationError, // 401 - Not authenticated
168
+ ForbiddenError, // 403 - No permission
169
+ NotFoundError, // 404 - Resource not found
170
+ ConflictError, // 409 - Resource conflict
171
+ UnprocessableEntityError,// 422 - Semantic error
172
+ RateLimitError, // 429 - Too many requests
173
+ InternalServerError, // 500 - Server error
174
+ ServiceUnavailableError, // 503 - Service down
175
+ DatabaseError, // 500 - Database issue
176
+ ExternalAPIError, // 502 - External API failed
177
+ } from '90dc-core';
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Common Config Schemas
183
+
184
+ Reusable schemas for common configurations:
185
+
186
+ ```typescript
187
+ import { CommonSchemas } from '90dc-core';
188
+
189
+ const ConfigSchema = BaseConfigSchema
190
+ .extend(CommonSchemas.redis.shape)
191
+ .extend(CommonSchemas.jwt.shape)
192
+ .extend(CommonSchemas.googleOAuth.shape);
193
+ ```
194
+
195
+ Available common schemas:
196
+ - `CommonSchemas.database` - PostgreSQL configuration
197
+ - `CommonSchemas.redis` - Redis configuration
198
+ - `CommonSchemas.jwt` - JWT authentication
199
+ - `CommonSchemas.googleOAuth` - Google OAuth
200
+ - `CommonSchemas.appleOAuth` - Apple OAuth
201
+ - `CommonSchemas.serviceUrls` - Inter-service URLs
202
+ - `CommonSchemas.featureFlags` - Feature flags
203
+
204
+ ---
205
+
206
+ ## Full Documentation
207
+
208
+ See **[USAGE_EXAMPLES.md](./USAGE_EXAMPLES.md)** for:
209
+ - Complete usage examples
210
+ - Migration guide from old code
211
+ - Best practices
212
+ - Testing examples
213
+ - All available features
214
+
215
+ ---
216
+
217
+ ## Exports
218
+
219
+ ### Configuration
220
+ ```typescript
221
+ import {
222
+ ConfigValidator,
223
+ BaseConfigSchema,
224
+ CommonSchemas,
225
+ createConfig,
226
+ ConfigurationError
227
+ } from '90dc-core';
228
+ ```
229
+
230
+ ### Errors
231
+ ```typescript
232
+ import {
233
+ AppError,
234
+ ValidationError,
235
+ AuthenticationError,
236
+ ForbiddenError,
237
+ NotFoundError,
238
+ ConflictError,
239
+ UnprocessableEntityError,
240
+ RateLimitError,
241
+ InternalServerError,
242
+ ServiceUnavailableError,
243
+ DatabaseError,
244
+ ExternalAPIError,
245
+ isAppError,
246
+ isOperationalError,
247
+ toAppError
248
+ } from '90dc-core';
249
+ ```
250
+
251
+ ### Middleware
252
+ ```typescript
253
+ import {
254
+ createErrorMiddleware,
255
+ validateRequest,
256
+ asyncHandler,
257
+ type ErrorMiddlewareConfig
258
+ } from '90dc-core';
259
+ ```
260
+
261
+ ### Database Models
262
+ ```typescript
263
+ import {
264
+ PersistedUser,
265
+ Program,
266
+ Workout,
267
+ Exercise,
268
+ Badge,
269
+ // ... many more
270
+ } from '90dc-core';
271
+ ```
272
+
273
+ ### Utilities
274
+ ```typescript
275
+ import {
276
+ AuthenticationUtil,
277
+ NotificationsUtil,
278
+ NotificationClient
279
+ } from '90dc-core';
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Migration Steps
285
+
286
+ 1. **Install dependencies**:
287
+ ```bash
288
+ npm install 90dc-core@latest zod
289
+ ```
290
+
291
+ 2. **Add config validation**:
292
+ ```typescript
293
+ import { BaseConfigSchema, ConfigValidator } from '90dc-core';
294
+ const config = new ConfigValidator(BaseConfigSchema);
295
+ ```
296
+
297
+ 3. **Add error middleware**:
298
+ ```typescript
299
+ import { createErrorMiddleware } from '90dc-core';
300
+ app.use(createErrorMiddleware());
301
+ ```
302
+
303
+ 4. **Replace error handling**:
304
+ ```typescript
305
+ // Before
306
+ if (!user) {
307
+ ctx.status = 404;
308
+ ctx.body = { message: 'Not found' };
309
+ return;
310
+ }
311
+
312
+ // After
313
+ import { NotFoundError } from '90dc-core';
314
+ if (!user) {
315
+ throw new NotFoundError('User');
316
+ }
317
+ ```
318
+
319
+ 5. **Add request validation**:
320
+ ```typescript
321
+ import { validateRequest } from '90dc-core';
322
+ router.post('/users',
323
+ validateRequest({ body: CreateUserSchema }),
324
+ handler
325
+ );
326
+ ```
327
+
328
+ See **[USAGE_EXAMPLES.md](./USAGE_EXAMPLES.md)** for detailed migration guide.
329
+
330
+ ---
331
+
332
+ ## Building & Publishing
333
+
334
+ ```bash
335
+ # Install dependencies
336
+ npm install
337
+
338
+ # Build
339
+ npm run build
340
+
341
+ # Publish to npm
342
+ npm run publish:build
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Changelog
348
+
349
+ See **[CHANGELOG.md](./CHANGELOG.md)** for version history.
350
+
351
+ ---
352
+
353
+ ## License
354
+
355
+ ISC
356
+
357
+ ---
358
+
359
+ ## Contributing
360
+
361
+ This is part of the 90 Day Challenge microservices architecture. See the main refactor plan for contribution guidelines.
package/dist/index.d.ts CHANGED
@@ -23,4 +23,9 @@ export * from "./lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js";
23
23
  export { AuthenticationUtil } from "./lib/utils/AuthenticationUtil.js";
24
24
  export { NotificationsUtil } from "./lib/utils/NotificationsUtil.js";
25
25
  export { NotificationClient } from "./lib/utils/NotificationClient.js";
26
+ export { Log } from "./lib/utils/Logger.js";
27
+ export { ConfigValidator, BaseConfigSchema, CommonSchemas, createConfig, ConfigurationError, type BaseConfig } from "./lib/config/ConfigValidator.js";
28
+ export { AppError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, ServiceUnavailableError, DatabaseError, ExternalAPIError, isAppError, isOperationalError, toAppError } from "./lib/Errors/AppError.js";
29
+ export { createErrorMiddleware, type ErrorMiddlewareConfig } from "./lib/middlewares/ErrorMiddleware.js";
30
+ export { RedisClient } from "./lib/classes/Redis.js";
26
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,yBAAyB,CAAA;AACvC,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,0CAA0C,CAAA;AACxD,cAAc,oCAAoC,CAAA;AAClD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uDAAuD,CAAA;AACrE,cAAc,2EAA2E,CAAA;AACzF,cAAc,8DAA8D,CAAA;AAC5E,cAAc,iEAAiE,CAAA;AAC/E,cAAc,2DAA2D,CAAA;AAIzE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,yBAAyB,CAAA;AACvC,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,0CAA0C,CAAA;AACxD,cAAc,oCAAoC,CAAA;AAClD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uDAAuD,CAAA;AACrE,cAAc,2EAA2E,CAAA;AACzF,cAAc,8DAA8D,CAAA;AAC5E,cAAc,iEAAiE,CAAA;AAC/E,cAAc,2DAA2D,CAAA;AAGzE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAA;AAGzC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,UAAU,EACX,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EACL,qBAAqB,EACrB,KAAK,qBAAqB,EAC3B,MAAM,sCAAsC,CAAA;AAG7C,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA"}
package/dist/index.js CHANGED
@@ -26,5 +26,13 @@ export * from "./lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js";
26
26
  export { AuthenticationUtil } from "./lib/utils/AuthenticationUtil.js";
27
27
  export { NotificationsUtil } from "./lib/utils/NotificationsUtil.js";
28
28
  export { NotificationClient } from "./lib/utils/NotificationClient.js";
29
+ export { Log } from "./lib/utils/Logger.js";
30
+ //Config
31
+ export { ConfigValidator, BaseConfigSchema, CommonSchemas, createConfig, ConfigurationError } from "./lib/config/ConfigValidator.js";
32
+ //Errors
33
+ export { AppError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, ServiceUnavailableError, DatabaseError, ExternalAPIError, isAppError, isOperationalError, toAppError } from "./lib/Errors/AppError.js";
34
+ //Middlewares
35
+ export { createErrorMiddleware } from "./lib/middlewares/ErrorMiddleware.js";
36
+ export { RedisClient } from "./lib/classes/Redis.js";
29
37
 
30
38
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models\nexport * from \"./lib/dbmodels/UsersFriends.js\"\nexport * from \"./lib/dbmodels/PersistedUser.js\"\nexport * from \"./lib/dbmodels/UserBadges.js\"\nexport * from \"./lib/dbmodels/UserStreaks.js\"\nexport * from \"./lib/dbmodels/Badge.js\"\nexport * from \"./lib/dbmodels/program/Program.js\"\nexport * from \"./lib/dbmodels/program/Workout.js\"\nexport * from \"./lib/dbmodels/program/Exercise.js\"\nexport * from \"./lib/dbmodels/program/ExerciseModels.js\"\nexport * from \"./lib/dbmodels/program/Superset.js\"\nexport * from \"./lib/dbmodels/Subscription.js\"\nexport * from \"./lib/dbmodels/SubscriptionLog.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/NonConsumableProgram.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js\"\n\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\n"],"names":["AuthenticationUtil","NotificationsUtil","NotificationClient"],"mappings":"AAAA,YAAY;AACZ,cAAc,oCAAoC;AAClD,cAAc,qCAAqC;AACnD,cAAc,oCAAoC;AAClD,cAAc,iCAAiC;AAC/C,cAAc,yCAAyC;AAEvD,WAAW;AACX,cAAc,iCAAgC;AAC9C,cAAc,kCAAiC;AAC/C,cAAc,+BAA8B;AAC5C,cAAc,gCAA+B;AAC7C,cAAc,0BAAyB;AACvC,cAAc,oCAAmC;AACjD,cAAc,oCAAmC;AACjD,cAAc,qCAAoC;AAClD,cAAc,2CAA0C;AACxD,cAAc,qCAAoC;AAClD,cAAc,iCAAgC;AAC9C,cAAc,oCAAmC;AACjD,cAAc,wDAAuD;AACrE,cAAc,4EAA2E;AACzF,cAAc,+DAA8D;AAC5E,cAAc,kEAAiE;AAC/E,cAAc,4DAA2D;AAGzE,OAAO;AACP,SAAQA,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,iBAAiB,QAAO,mCAAkC;AAClE,SAAQC,kBAAkB,QAAO,oCAAmC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models\nexport * from \"./lib/dbmodels/UsersFriends.js\"\nexport * from \"./lib/dbmodels/PersistedUser.js\"\nexport * from \"./lib/dbmodels/UserBadges.js\"\nexport * from \"./lib/dbmodels/UserStreaks.js\"\nexport * from \"./lib/dbmodels/Badge.js\"\nexport * from \"./lib/dbmodels/program/Program.js\"\nexport * from \"./lib/dbmodels/program/Workout.js\"\nexport * from \"./lib/dbmodels/program/Exercise.js\"\nexport * from \"./lib/dbmodels/program/ExerciseModels.js\"\nexport * from \"./lib/dbmodels/program/Superset.js\"\nexport * from \"./lib/dbmodels/Subscription.js\"\nexport * from \"./lib/dbmodels/SubscriptionLog.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/NonConsumableProgram.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.js\"\nexport * from \"./lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js\"\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\nexport {Log} from \"./lib/utils/Logger.js\"\n\n//Config\nexport {\n ConfigValidator,\n BaseConfigSchema,\n CommonSchemas,\n createConfig,\n ConfigurationError,\n type BaseConfig\n} from \"./lib/config/ConfigValidator.js\"\n\n//Errors\nexport {\n AppError,\n ValidationError,\n AuthenticationError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n UnprocessableEntityError,\n RateLimitError,\n InternalServerError,\n ServiceUnavailableError,\n DatabaseError,\n ExternalAPIError,\n isAppError,\n isOperationalError,\n toAppError\n} from \"./lib/Errors/AppError.js\"\n\n//Middlewares\nexport {\n createErrorMiddleware,\n type ErrorMiddlewareConfig\n} from \"./lib/middlewares/ErrorMiddleware.js\"\n\n\nexport {RedisClient} from \"./lib/classes/Redis.js\""],"names":["AuthenticationUtil","NotificationsUtil","NotificationClient","Log","ConfigValidator","BaseConfigSchema","CommonSchemas","createConfig","ConfigurationError","AppError","ValidationError","AuthenticationError","ForbiddenError","NotFoundError","ConflictError","UnprocessableEntityError","RateLimitError","InternalServerError","ServiceUnavailableError","DatabaseError","ExternalAPIError","isAppError","isOperationalError","toAppError","createErrorMiddleware","RedisClient"],"mappings":"AAAA,YAAY;AACZ,cAAc,oCAAoC;AAClD,cAAc,qCAAqC;AACnD,cAAc,oCAAoC;AAClD,cAAc,iCAAiC;AAC/C,cAAc,yCAAyC;AAEvD,WAAW;AACX,cAAc,iCAAgC;AAC9C,cAAc,kCAAiC;AAC/C,cAAc,+BAA8B;AAC5C,cAAc,gCAA+B;AAC7C,cAAc,0BAAyB;AACvC,cAAc,oCAAmC;AACjD,cAAc,oCAAmC;AACjD,cAAc,qCAAoC;AAClD,cAAc,2CAA0C;AACxD,cAAc,qCAAoC;AAClD,cAAc,iCAAgC;AAC9C,cAAc,oCAAmC;AACjD,cAAc,wDAAuD;AACrE,cAAc,4EAA2E;AACzF,cAAc,+DAA8D;AAC5E,cAAc,kEAAiE;AAC/E,cAAc,4DAA2D;AAEzE,OAAO;AACP,SAAQA,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,iBAAiB,QAAO,mCAAkC;AAClE,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,GAAG,QAAO,wBAAuB;AAEzC,QAAQ;AACR,SACEC,eAAe,EACfC,gBAAgB,EAChBC,aAAa,EACbC,YAAY,EACZC,kBAAkB,QAEb,kCAAiC;AAExC,QAAQ;AACR,SACEC,QAAQ,EACRC,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,aAAa,EACbC,aAAa,EACbC,wBAAwB,EACxBC,cAAc,EACdC,mBAAmB,EACnBC,uBAAuB,EACvBC,aAAa,EACbC,gBAAgB,EAChBC,UAAU,EACVC,kBAAkB,EAClBC,UAAU,QACL,2BAA0B;AAEjC,aAAa;AACb,SACEC,qBAAqB,QAEhB,uCAAsC;AAG7C,SAAQC,WAAW,QAAO,yBAAwB"}
@@ -0,0 +1,47 @@
1
+ export declare class AppError extends Error {
2
+ readonly code: string;
3
+ readonly statusCode: number;
4
+ readonly details?: unknown | undefined;
5
+ readonly isOperational: boolean;
6
+ constructor(code: string, message: string, statusCode: number, details?: unknown | undefined, isOperational?: boolean);
7
+ toJSON(): {
8
+ error: Record<string, unknown>;
9
+ };
10
+ }
11
+ export declare class ValidationError extends AppError {
12
+ constructor(message: string, details?: unknown);
13
+ }
14
+ export declare class AuthenticationError extends AppError {
15
+ constructor(message?: string);
16
+ }
17
+ export declare class ForbiddenError extends AppError {
18
+ constructor(message?: string);
19
+ }
20
+ export declare class NotFoundError extends AppError {
21
+ constructor(resource?: string);
22
+ }
23
+ export declare class ConflictError extends AppError {
24
+ constructor(message: string, details?: unknown);
25
+ }
26
+ export declare class UnprocessableEntityError extends AppError {
27
+ constructor(message: string, details?: unknown);
28
+ }
29
+ export declare class RateLimitError extends AppError {
30
+ constructor(message?: string);
31
+ }
32
+ export declare class InternalServerError extends AppError {
33
+ constructor(message?: string, details?: unknown);
34
+ }
35
+ export declare class ServiceUnavailableError extends AppError {
36
+ constructor(service?: string);
37
+ }
38
+ export declare class DatabaseError extends AppError {
39
+ constructor(message: string, details?: unknown);
40
+ }
41
+ export declare class ExternalAPIError extends AppError {
42
+ constructor(service: string, message: string, statusCode?: number, details?: unknown);
43
+ }
44
+ export declare function isAppError(error: unknown): error is AppError;
45
+ export declare function isOperationalError(error: unknown): boolean;
46
+ export declare function toAppError(error: unknown): AppError;
47
+ //# sourceMappingURL=AppError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppError.d.ts","sourceRoot":"","sources":["../../../src/lib/Errors/AppError.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;aAIb,IAAI,EAAE,MAAM;aAEZ,UAAU,EAAE,MAAM;aAClB,OAAO,CAAC,EAAE,OAAO;IANrC,SAAgB,aAAa,EAAE,OAAO,CAAC;gBAGnB,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,OAAO,YAAA,EACjC,aAAa,UAAO;IAQxB,MAAM;;;CAYP;AAGD,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAG/C;AAGD,qBAAa,mBAAoB,SAAQ,QAAQ;gBACnC,OAAO,GAAE,MAAkC;CAGxD;AAGD,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,GAAE,MAA2B;CAGjD;AAGD,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,QAAQ,GAAE,MAAmB;CAG1C;AAGD,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAG/C;AAGD,qBAAa,wBAAyB,SAAQ,QAAQ;gBACxC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAG/C;AAED,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,GAAE,MAAoD;CAG1E;AAGD,qBAAa,mBAAoB,SAAQ,QAAQ;gBACnC,OAAO,GAAE,MAAgC,EAAE,OAAO,CAAC,EAAE,OAAO;CAGzE;AAGD,qBAAa,uBAAwB,SAAQ,QAAQ;gBACvC,OAAO,GAAE,MAAkB;CAGxC;AAGD,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAG/C;AAGD,qBAAa,gBAAiB,SAAQ,QAAQ;gBAE1C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAY,EACxB,OAAO,CAAC,EAAE,OAAO;CASpB;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAGD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK1D;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAenD"}
@@ -0,0 +1,104 @@
1
+ export class AppError extends Error {
2
+ code;
3
+ statusCode;
4
+ details;
5
+ isOperational;
6
+ constructor(code, message, statusCode, details, isOperational = true){
7
+ super(message), this.code = code, this.statusCode = statusCode, this.details = details;
8
+ this.name = new.target.name;
9
+ this.isOperational = isOperational;
10
+ Error.captureStackTrace?.(this, new.target);
11
+ }
12
+ toJSON() {
13
+ const error = {
14
+ code: this.code,
15
+ message: this.message
16
+ };
17
+ if (this.details !== undefined) {
18
+ error.details = this.details;
19
+ }
20
+ return {
21
+ error
22
+ };
23
+ }
24
+ }
25
+ export class ValidationError extends AppError {
26
+ constructor(message, details){
27
+ super('VALIDATION_ERROR', message, 400, details);
28
+ }
29
+ }
30
+ export class AuthenticationError extends AppError {
31
+ constructor(message = 'Authentication required'){
32
+ super('AUTHENTICATION_ERROR', message, 401);
33
+ }
34
+ }
35
+ export class ForbiddenError extends AppError {
36
+ constructor(message = 'Access forbidden'){
37
+ super('FORBIDDEN', message, 403);
38
+ }
39
+ }
40
+ export class NotFoundError extends AppError {
41
+ constructor(resource = 'Resource'){
42
+ super('NOT_FOUND', `${resource} not found`, 404);
43
+ }
44
+ }
45
+ export class ConflictError extends AppError {
46
+ constructor(message, details){
47
+ super('CONFLICT', message, 409, details);
48
+ }
49
+ }
50
+ export class UnprocessableEntityError extends AppError {
51
+ constructor(message, details){
52
+ super('UNPROCESSABLE_ENTITY', message, 422, details);
53
+ }
54
+ }
55
+ export class RateLimitError extends AppError {
56
+ constructor(message = 'Too many requests, please try again later'){
57
+ super('RATE_LIMIT_EXCEEDED', message, 429);
58
+ }
59
+ }
60
+ export class InternalServerError extends AppError {
61
+ constructor(message = 'Internal server error', details){
62
+ super('INTERNAL_ERROR', message, 500, details, false);
63
+ }
64
+ }
65
+ export class ServiceUnavailableError extends AppError {
66
+ constructor(service = 'Service'){
67
+ super('SERVICE_UNAVAILABLE', `${service} is temporarily unavailable`, 503);
68
+ }
69
+ }
70
+ export class DatabaseError extends AppError {
71
+ constructor(message, details){
72
+ super('DATABASE_ERROR', message, 500, details, false);
73
+ }
74
+ }
75
+ export class ExternalAPIError extends AppError {
76
+ constructor(service, message, statusCode = 502, details){
77
+ super('EXTERNAL_API_ERROR', `${service}: ${message}`, statusCode, details);
78
+ }
79
+ }
80
+ export function isAppError(error) {
81
+ return error instanceof AppError;
82
+ }
83
+ export function isOperationalError(error) {
84
+ if (error instanceof AppError) {
85
+ return error.isOperational;
86
+ }
87
+ return false;
88
+ }
89
+ export function toAppError(error) {
90
+ if (error instanceof AppError) {
91
+ return error;
92
+ }
93
+ if (error instanceof Error) {
94
+ return new InternalServerError(error.message, {
95
+ originalError: error.name,
96
+ stack: error.stack
97
+ });
98
+ }
99
+ return new InternalServerError('An unexpected error occurred', {
100
+ error: String(error)
101
+ });
102
+ }
103
+
104
+ //# sourceMappingURL=AppError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/Errors/AppError.ts"],"sourcesContent":["export class AppError extends Error {\n public readonly isOperational: boolean;\n\n constructor(\n public readonly code: string,\n message: string,\n public readonly statusCode: number,\n public readonly details?: unknown,\n isOperational = true\n ) {\n super(message);\n this.name = new.target.name;\n this.isOperational = isOperational;\n Error.captureStackTrace?.(this, new.target);\n }\n\n toJSON() {\n const error: Record<string, unknown> = {\n code: this.code,\n message: this.message,\n };\n\n if (this.details !== undefined) {\n error.details = this.details;\n }\n\n return { error };\n }\n}\n\n\nexport class ValidationError extends AppError {\n constructor(message: string, details?: unknown) {\n super('VALIDATION_ERROR', message, 400, details);\n }\n}\n\n\nexport class AuthenticationError extends AppError {\n constructor(message: string = 'Authentication required') {\n super('AUTHENTICATION_ERROR', message, 401);\n }\n}\n\n\nexport class ForbiddenError extends AppError {\n constructor(message: string = 'Access forbidden') {\n super('FORBIDDEN', message, 403);\n }\n}\n\n\nexport class NotFoundError extends AppError {\n constructor(resource: string = 'Resource') {\n super('NOT_FOUND', `${resource} not found`, 404);\n }\n}\n\n\nexport class ConflictError extends AppError {\n constructor(message: string, details?: unknown) {\n super('CONFLICT', message, 409, details);\n }\n}\n\n\nexport class UnprocessableEntityError extends AppError {\n constructor(message: string, details?: unknown) {\n super('UNPROCESSABLE_ENTITY', message, 422, details);\n }\n}\n\nexport class RateLimitError extends AppError {\n constructor(message: string = 'Too many requests, please try again later') {\n super('RATE_LIMIT_EXCEEDED', message, 429);\n }\n}\n\n\nexport class InternalServerError extends AppError {\n constructor(message: string = 'Internal server error', details?: unknown) {\n super('INTERNAL_ERROR', message, 500, details, false);\n }\n}\n\n\nexport class ServiceUnavailableError extends AppError {\n constructor(service: string = 'Service') {\n super('SERVICE_UNAVAILABLE', `${service} is temporarily unavailable`, 503);\n }\n}\n\n\nexport class DatabaseError extends AppError {\n constructor(message: string, details?: unknown) {\n super('DATABASE_ERROR', message, 500, details, false);\n }\n}\n\n\nexport class ExternalAPIError extends AppError {\n constructor(\n service: string,\n message: string,\n statusCode: number = 502,\n details?: unknown\n ) {\n super(\n 'EXTERNAL_API_ERROR',\n `${service}: ${message}`,\n statusCode,\n details\n );\n }\n}\n\n\nexport function isAppError(error: unknown): error is AppError {\n return error instanceof AppError;\n}\n\n\nexport function isOperationalError(error: unknown): boolean {\n if (error instanceof AppError) {\n return error.isOperational;\n }\n return false;\n}\n\n\nexport function toAppError(error: unknown): AppError {\n if (error instanceof AppError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new InternalServerError(error.message, {\n originalError: error.name,\n stack: error.stack,\n });\n }\n\n return new InternalServerError('An unexpected error occurred', {\n error: String(error),\n });\n}\n"],"names":["AppError","Error","isOperational","code","message","statusCode","details","name","captureStackTrace","toJSON","error","undefined","ValidationError","AuthenticationError","ForbiddenError","NotFoundError","resource","ConflictError","UnprocessableEntityError","RateLimitError","InternalServerError","ServiceUnavailableError","service","DatabaseError","ExternalAPIError","isAppError","isOperationalError","toAppError","originalError","stack","String"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { Redis } from "ioredis";
2
+ export declare class RedisClient {
3
+ private static instance;
4
+ private constructor();
5
+ static getInstance(): Redis;
6
+ static get(key: string): Promise<string | null>;
7
+ static set(key: string, value: string, expirySeconds?: number): Promise<string>;
8
+ static del(key: string): Promise<number>;
9
+ static getJSON<T>(key: string): Promise<T | null>;
10
+ static setJSON(key: string, value: any, expirySeconds?: number): Promise<string>;
11
+ static waitForFill<T>(key: string, timeoutMs?: number, pollMs?: number): Promise<T | null>;
12
+ }
13
+ //# sourceMappingURL=Redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Redis.d.ts","sourceRoot":"","sources":["../../../src/lib/classes/Redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAMhC,qBAAa,WAAW;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAQ;IAE/B,OAAO;WAEO,WAAW,IAAI,KAAK;WA2Bd,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;WAKxC,GAAG,CACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC;WAQE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAKjC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAK1C,OAAO,CACvB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,GAAG,EACV,aAAa,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC;WAKE,WAAW,CAAC,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,SAAS,SAAO,EAChB,MAAM,SAAM,GACb,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CASvB"}
@@ -0,0 +1,63 @@
1
+ import { Redis } from "ioredis";
2
+ import { Log } from "../utils/Logger.js";
3
+ const logger = Log.getInstance().extend("redis-client");
4
+ export class RedisClient {
5
+ static instance;
6
+ constructor(){}
7
+ static getInstance() {
8
+ if (!RedisClient.instance) {
9
+ const REDIS_HOST = process.env.REDIS_HOST || "localhost";
10
+ const REDIS_PORT = process.env.REDIS_PORT || 6379;
11
+ const options = {
12
+ host: REDIS_HOST,
13
+ port: Number(REDIS_PORT),
14
+ maxRetriesPerRequest: null,
15
+ enableAutoPipelining: true,
16
+ connectTimeout: 5000,
17
+ lazyConnect: false
18
+ };
19
+ RedisClient.instance = new Redis(options);
20
+ RedisClient.instance.on("connect", ()=>{
21
+ logger.info("Connected to Redis");
22
+ });
23
+ RedisClient.instance.on("error", (err)=>{
24
+ logger.error("Redis error:", err);
25
+ });
26
+ logger.info(`Redis status: ${RedisClient.instance.status}`);
27
+ }
28
+ return RedisClient.instance;
29
+ }
30
+ static async get(key) {
31
+ const client = this.getInstance();
32
+ return client.get(key);
33
+ }
34
+ static async set(key, value, expirySeconds) {
35
+ const client = this.getInstance();
36
+ if (expirySeconds) {
37
+ return client.set(key, value, "EX", expirySeconds);
38
+ }
39
+ return client.set(key, value);
40
+ }
41
+ static async del(key) {
42
+ const client = this.getInstance();
43
+ return client.del(key);
44
+ }
45
+ static async getJSON(key) {
46
+ const raw = await this.get(key);
47
+ return raw ? JSON.parse(raw) : null;
48
+ }
49
+ static async setJSON(key, value, expirySeconds) {
50
+ return this.set(key, JSON.stringify(value), expirySeconds);
51
+ }
52
+ static async waitForFill(key, timeoutMs = 8000, pollMs = 150) {
53
+ const start = Date.now();
54
+ while(Date.now() - start < timeoutMs){
55
+ const v = await this.getJSON(key);
56
+ if (v) return v;
57
+ await new Promise((r)=>setTimeout(r, pollMs));
58
+ }
59
+ return null;
60
+ }
61
+ }
62
+
63
+ //# sourceMappingURL=Redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/classes/Redis.ts"],"sourcesContent":["import { Redis } from \"ioredis\";\nimport type { RedisOptions } from \"ioredis\";\nimport {Log} from \"../utils/Logger.js\";\n\nconst logger: Log = Log.getInstance().extend(\"redis-client\");\n\nexport class RedisClient {\n private static instance: Redis;\n\n private constructor() {}\n\n public static getInstance(): Redis {\n if (!RedisClient.instance) {\n const REDIS_HOST = process.env.REDIS_HOST || \"localhost\";\n const REDIS_PORT = process.env.REDIS_PORT || 6379;\n\n const options: RedisOptions = {\n host: REDIS_HOST,\n port: Number(REDIS_PORT),\n maxRetriesPerRequest: null,\n enableAutoPipelining: true,\n connectTimeout: 5000,\n lazyConnect: false,\n };\n\n RedisClient.instance = new Redis(options);\n\n RedisClient.instance.on(\"connect\", () => {\n logger.info(\"Connected to Redis\");\n });\n RedisClient.instance.on(\"error\", (err) => {\n logger.error(\"Redis error:\", err);\n });\n logger.info(`Redis status: ${RedisClient.instance.status}`);\n }\n return RedisClient.instance;\n }\n\n public static async get(key: string): Promise<string | null> {\n const client = this.getInstance();\n return client.get(key);\n }\n\n public static async set(\n key: string,\n value: string,\n expirySeconds?: number,\n ): Promise<string> {\n const client = this.getInstance();\n if (expirySeconds) {\n return client.set(key, value, \"EX\", expirySeconds);\n }\n return client.set(key, value);\n }\n\n public static async del(key: string): Promise<number> {\n const client = this.getInstance();\n return client.del(key);\n }\n\n public static async getJSON<T>(key: string): Promise<T | null> {\n const raw = await this.get(key);\n return raw ? (JSON.parse(raw) as T) : null;\n }\n\n public static async setJSON(\n key: string,\n value: any,\n expirySeconds?: number,\n ): Promise<string> {\n return this.set(key, JSON.stringify(value), expirySeconds);\n }\n\n\n public static async waitForFill<T>(\n key: string,\n timeoutMs = 8000,\n pollMs = 150,\n ): Promise<T | null> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const v = await this.getJSON<T>(key);\n if (v) return v;\n await new Promise((r) => setTimeout(r, pollMs));\n }\n return null;\n }\n}"],"names":["Redis","Log","logger","getInstance","extend","RedisClient","instance","REDIS_HOST","process","env","REDIS_PORT","options","host","port","Number","maxRetriesPerRequest","enableAutoPipelining","connectTimeout","lazyConnect","on","info","err","error","status","get","key","client","set","value","expirySeconds","del","getJSON","raw","JSON","parse","setJSON","stringify","waitForFill","timeoutMs","pollMs","start","Date","now","v","Promise","r","setTimeout"],"mappings":"AAAA,SAASA,KAAK,QAAQ,UAAU;AAEhC,SAAQC,GAAG,QAAO,qBAAqB;AAEvC,MAAMC,SAAcD,IAAIE,WAAW,GAAGC,MAAM,CAAC;AAE7C,OAAO,MAAMC;IACT,OAAeC,SAAgB;IAE/B,aAAsB,CAAC;IAEvB,OAAcH,cAAqB;QAC/B,IAAI,CAACE,YAAYC,QAAQ,EAAE;YACvB,MAAMC,aAAaC,QAAQC,GAAG,CAACF,UAAU,IAAI;YAC7C,MAAMG,aAAaF,QAAQC,GAAG,CAACC,UAAU,IAAI;YAE7C,MAAMC,UAAwB;gBAC1BC,MAAML;gBACNM,MAAMC,OAAOJ;gBACbK,sBAAsB;gBACtBC,sBAAsB;gBACtBC,gBAAgB;gBAChBC,aAAa;YACjB;YAEAb,YAAYC,QAAQ,GAAG,IAAIN,MAAMW;YAEjCN,YAAYC,QAAQ,CAACa,EAAE,CAAC,WAAW;gBAC/BjB,OAAOkB,IAAI,CAAC;YAChB;YACAf,YAAYC,QAAQ,CAACa,EAAE,CAAC,SAAS,CAACE;gBAC9BnB,OAAOoB,KAAK,CAAC,gBAAgBD;YACjC;YACAnB,OAAOkB,IAAI,CAAC,CAAC,cAAc,EAAEf,YAAYC,QAAQ,CAACiB,MAAM,EAAE;QAC9D;QACA,OAAOlB,YAAYC,QAAQ;IAC/B;IAEA,aAAoBkB,IAAIC,GAAW,EAA0B;QACzD,MAAMC,SAAS,IAAI,CAACvB,WAAW;QAC/B,OAAOuB,OAAOF,GAAG,CAACC;IACtB;IAEA,aAAoBE,IAChBF,GAAW,EACXG,KAAa,EACbC,aAAsB,EACP;QACf,MAAMH,SAAS,IAAI,CAACvB,WAAW;QAC/B,IAAI0B,eAAe;YACf,OAAOH,OAAOC,GAAG,CAACF,KAAKG,OAAO,MAAMC;QACxC;QACA,OAAOH,OAAOC,GAAG,CAACF,KAAKG;IAC3B;IAEA,aAAoBE,IAAIL,GAAW,EAAmB;QAClD,MAAMC,SAAS,IAAI,CAACvB,WAAW;QAC/B,OAAOuB,OAAOI,GAAG,CAACL;IACtB;IAEA,aAAoBM,QAAWN,GAAW,EAAqB;QAC3D,MAAMO,MAAM,MAAM,IAAI,CAACR,GAAG,CAACC;QAC3B,OAAOO,MAAOC,KAAKC,KAAK,CAACF,OAAa;IAC1C;IAEA,aAAoBG,QAChBV,GAAW,EACXG,KAAU,EACVC,aAAsB,EACP;QACf,OAAO,IAAI,CAACF,GAAG,CAACF,KAAKQ,KAAKG,SAAS,CAACR,QAAQC;IAChD;IAGA,aAAoBQ,YAChBZ,GAAW,EACXa,YAAY,IAAI,EAChBC,SAAS,GAAG,EACK;QACjB,MAAMC,QAAQC,KAAKC,GAAG;QACtB,MAAOD,KAAKC,GAAG,KAAKF,QAAQF,UAAW;YACnC,MAAMK,IAAI,MAAM,IAAI,CAACZ,OAAO,CAAIN;YAChC,IAAIkB,GAAG,OAAOA;YACd,MAAM,IAAIC,QAAQ,CAACC,IAAMC,WAAWD,GAAGN;QAC3C;QACA,OAAO;IACX;AACJ"}