@shadimakhoul/ggcoach 1.0.21 → 1.0.22
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 +233 -0
- package/dist/types/auth.d.ts +11 -3
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/utils/jwt.d.ts +4 -4
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +20 -47
- package/dist/utils/jwt.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @shadimakhoul/ggcoach
|
|
2
|
+
|
|
3
|
+
Shared utilities, middleware, and types for Node.js/Express microservices.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @shadimakhoul/ggcoach
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [JWT](#jwt)
|
|
14
|
+
- [Middleware](#middleware)
|
|
15
|
+
- [Config & Bootstrap](#config--bootstrap)
|
|
16
|
+
- [Logger](#logger)
|
|
17
|
+
- [Types](#types)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## JWT
|
|
22
|
+
|
|
23
|
+
Generic JWT helpers. You define the payload shape; the library handles signing and verification.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { generateToken, generateRefreshToken, verifyToken, JWTConfig, BaseJWTPayload } from '@shadimakhoul/ggcoach';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `JWTConfig`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
interface JWTConfig {
|
|
33
|
+
secret: string;
|
|
34
|
+
expiresIn: string; // e.g. '1h', '7d'
|
|
35
|
+
issuer: string; // e.g. 'my-auth-service'
|
|
36
|
+
audience: string; // e.g. 'my-app'
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `BaseJWTPayload`
|
|
41
|
+
|
|
42
|
+
The minimum shape required by the library. Extend it with your own fields.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
interface BaseJWTPayload {
|
|
46
|
+
tokenType?: 'access' | 'refresh';
|
|
47
|
+
tokenVersion?: number;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Usage
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// 1. Define your payload
|
|
55
|
+
interface MyPayload extends BaseJWTPayload {
|
|
56
|
+
userId: string;
|
|
57
|
+
role: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const config: JWTConfig = {
|
|
61
|
+
secret: process.env.JWT_SECRET!,
|
|
62
|
+
expiresIn: '1h',
|
|
63
|
+
issuer: 'my-auth-service',
|
|
64
|
+
audience: 'my-app',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// 2. Issue tokens
|
|
68
|
+
const accessToken = generateToken<MyPayload>(config, { userId: '123', role: 'admin' });
|
|
69
|
+
const refreshToken = generateRefreshToken<MyPayload>(config, { userId: '123', role: 'admin' });
|
|
70
|
+
// tokenType is stamped automatically: 'access' / 'refresh'
|
|
71
|
+
|
|
72
|
+
// 3. Verify
|
|
73
|
+
const decoded = verifyToken<MyPayload>(config, accessToken);
|
|
74
|
+
// decoded.tokenType === 'access'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> `verifyToken` throws `Error('Invalid or expired token')` on failure.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Middleware
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import {
|
|
85
|
+
authenticateToken,
|
|
86
|
+
optionalAuth,
|
|
87
|
+
requireAdmin,
|
|
88
|
+
requireSuperAdmin,
|
|
89
|
+
requireInternalToken,
|
|
90
|
+
createRateLimiter,
|
|
91
|
+
authRateLimiter,
|
|
92
|
+
generalRateLimiter,
|
|
93
|
+
errorHandler,
|
|
94
|
+
createCorsOptions,
|
|
95
|
+
corsMiddleware,
|
|
96
|
+
} from '@shadimakhoul/ggcoach';
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `authenticateToken(JWT_SECRET)`
|
|
100
|
+
|
|
101
|
+
Express middleware that reads a `Bearer` token from `Authorization`, verifies it, checks the token version against Redis, and sets `req.user`.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
app.use(authenticateToken(process.env.JWT_SECRET!));
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `optionalAuth(JWT_SECRET)`
|
|
108
|
+
|
|
109
|
+
Same as `authenticateToken` but does not reject the request if no token is provided. `req.user` is set only when a valid token is present.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
app.use(optionalAuth(process.env.JWT_SECRET!));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `requireAdmin` / `requireSuperAdmin`
|
|
116
|
+
|
|
117
|
+
Guards that check `req.user.accountType`. Must be placed after `authenticateToken`.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
router.get('/admin/users', authenticateToken(secret), requireAdmin, handler);
|
|
121
|
+
router.post('/admin/promote', authenticateToken(secret), requireSuperAdmin, handler);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `requireInternalToken(ALLOW_DIRECT_ACCESS, INTERNAL_SERVICE_TOKEN)`
|
|
125
|
+
|
|
126
|
+
Validates the `x-internal-token` header for service-to-service calls.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
app.use(requireInternalToken(
|
|
130
|
+
process.env.ALLOW_DIRECT_ACCESS === 'true',
|
|
131
|
+
process.env.INTERNAL_SERVICE_TOKEN!,
|
|
132
|
+
));
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Rate limiters
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// Pre-built limiters
|
|
139
|
+
app.use('/auth', authRateLimiter); // 5 req / 15 min
|
|
140
|
+
app.use(generalRateLimiter); // 1000 req / 15 min
|
|
141
|
+
|
|
142
|
+
// Custom limiter
|
|
143
|
+
const customLimiter = createRateLimiter(
|
|
144
|
+
5 * 60 * 1000, // windowMs
|
|
145
|
+
20, // max requests
|
|
146
|
+
);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `errorHandler`
|
|
150
|
+
|
|
151
|
+
Centralised Express error handler. Register as the last middleware.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
app.use(errorHandler);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `createCorsOptions(allowedOrigins?, allowedHosts?)`
|
|
158
|
+
|
|
159
|
+
Returns a CORS options object. Passing no arguments allows all origins.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// Allow specific origins and wildcard sub-domains
|
|
163
|
+
app.use(cors(createCorsOptions(
|
|
164
|
+
['https://app.example.com'],
|
|
165
|
+
['example.com'], // matches *.example.com
|
|
166
|
+
)));
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Config & Bootstrap
|
|
172
|
+
|
|
173
|
+
### `initExpress(isDevelopment, allowedOrigins?, allowedHosts?)`
|
|
174
|
+
|
|
175
|
+
Creates a pre-configured Express app with Helmet, CORS, JSON body parsing, rate limiting, and Morgan logging.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { initExpress } from '@shadimakhoul/ggcoach';
|
|
179
|
+
|
|
180
|
+
const app = initExpress(
|
|
181
|
+
process.env.NODE_ENV === 'development',
|
|
182
|
+
['https://app.example.com'],
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Redis
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { initRedis, getRedisClient } from '@shadimakhoul/ggcoach';
|
|
190
|
+
|
|
191
|
+
await initRedis(process.env.REDIS_URL!);
|
|
192
|
+
const client = getRedisClient();
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Logger
|
|
198
|
+
|
|
199
|
+
Winston logger pre-configured with console + file transports.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { logger } from '@shadimakhoul/ggcoach';
|
|
203
|
+
|
|
204
|
+
logger.info('Server started');
|
|
205
|
+
logger.error('Something failed', { error });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Log files are written to `./logs/combined.log` and `./logs/error.log`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Types
|
|
213
|
+
|
|
214
|
+
Key types exported from the package:
|
|
215
|
+
|
|
216
|
+
| Type | Description |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `JWTConfig` | Config object for all JWT helpers |
|
|
219
|
+
| `BaseJWTPayload` | Minimum payload shape — extend for your project |
|
|
220
|
+
| `JWTPayload` | ggCoach-specific payload extending `BaseJWTPayload` |
|
|
221
|
+
| `UserAttributes` | Full ggCoach user model shape |
|
|
222
|
+
| `UserCreationAttributes` | `UserAttributes` minus auto-generated fields |
|
|
223
|
+
| `AuthResponse` | Standard auth endpoint response envelope |
|
|
224
|
+
| `AccountType` | Enum: `User`, `Admin`, `SuperAdmin`, etc. |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Publishing
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npm run build # compile TypeScript to dist/
|
|
232
|
+
npm publish # runs build first via prepublishOnly
|
|
233
|
+
```
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { AccountType } from "./enums";
|
|
2
|
-
export interface
|
|
2
|
+
export interface JWTConfig {
|
|
3
|
+
secret: string;
|
|
4
|
+
expiresIn: string;
|
|
5
|
+
issuer: string;
|
|
6
|
+
audience: string;
|
|
7
|
+
}
|
|
8
|
+
export interface BaseJWTPayload {
|
|
9
|
+
tokenType?: 'access' | 'refresh';
|
|
10
|
+
tokenVersion?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface JWTPayload extends BaseJWTPayload {
|
|
3
13
|
id: number;
|
|
4
14
|
email: string;
|
|
5
15
|
phoneNumber: string | null;
|
|
@@ -9,8 +19,6 @@ export interface JWTPayload {
|
|
|
9
19
|
is_active: boolean;
|
|
10
20
|
emailVerified: boolean;
|
|
11
21
|
phoneVerified: boolean;
|
|
12
|
-
tokenVersion?: number;
|
|
13
|
-
tokenType?: 'access' | 'refresh';
|
|
14
22
|
}
|
|
15
23
|
export interface UserAttributes {
|
|
16
24
|
id: number;
|
package/dist/types/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../types/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../types/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,UAAU,CAAC;SACnB;KACF;CACF;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAClD,cAAc,EACZ,IAAI,GACJ,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,iBAAiB,GACjB,eAAe,GACf,iBAAiB,CACpB;CAAG;AAEJ,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QACvC,MAAM,EAAE;YACN,YAAY,EAAE,MAAM,CAAC;YACrB,aAAa,EAAE,MAAM,CAAC;SACvB,CAAC;KACH,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
package/dist/utils/jwt.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const generateToken:
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
1
|
+
import { BaseJWTPayload, JWTConfig } from '../types';
|
|
2
|
+
export declare const generateToken: <T extends BaseJWTPayload>(config: JWTConfig, payload: T) => string;
|
|
3
|
+
export declare const generateRefreshToken: <T extends BaseJWTPayload>(config: JWTConfig, payload: T) => string;
|
|
4
|
+
export declare const verifyToken: <T extends BaseJWTPayload>(config: JWTConfig, token: string) => T;
|
|
5
5
|
//# sourceMappingURL=jwt.d.ts.map
|
package/dist/utils/jwt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../utils/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../utils/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErD,eAAO,MAAM,aAAa,qCAAsC,SAAS,iBAAe,MAQvF,CAAC;AAEF,eAAO,MAAM,oBAAoB,qCAAsC,SAAS,iBAAe,MAQ9F,CAAC;AAEF,eAAO,MAAM,WAAW,qCAAsC,SAAS,SAAS,MAAM,MASrF,CAAC"}
|
package/dist/utils/jwt.js
CHANGED
|
@@ -3,63 +3,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.verifyToken = exports.generateRefreshToken = exports.generateToken = void 0;
|
|
7
7
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
-
const generateToken = (
|
|
9
|
-
const payload = {
|
|
10
|
-
id: user.id,
|
|
11
|
-
email: user.email,
|
|
12
|
-
phoneNumber: user.phoneNumber,
|
|
13
|
-
firstName: user.firstName,
|
|
14
|
-
lastName: user.lastName,
|
|
15
|
-
accountType: user.accountType,
|
|
16
|
-
is_active: user.is_active,
|
|
17
|
-
emailVerified: user.emailVerified,
|
|
18
|
-
phoneVerified: user.phoneVerified,
|
|
19
|
-
tokenVersion: user.tokenVersion ?? 0,
|
|
20
|
-
tokenType: 'access',
|
|
21
|
-
};
|
|
8
|
+
const generateToken = (config, payload) => {
|
|
22
9
|
const options = {
|
|
23
|
-
expiresIn:
|
|
24
|
-
issuer:
|
|
25
|
-
audience:
|
|
10
|
+
expiresIn: config.expiresIn,
|
|
11
|
+
issuer: config.issuer,
|
|
12
|
+
audience: config.audience,
|
|
26
13
|
};
|
|
27
|
-
return jsonwebtoken_1.default.sign(payload,
|
|
14
|
+
return jsonwebtoken_1.default.sign({ ...payload, tokenType: 'access' }, config.secret, options);
|
|
28
15
|
};
|
|
29
16
|
exports.generateToken = generateToken;
|
|
30
|
-
const
|
|
17
|
+
const generateRefreshToken = (config, payload) => {
|
|
18
|
+
const options = {
|
|
19
|
+
expiresIn: config.expiresIn,
|
|
20
|
+
issuer: config.issuer,
|
|
21
|
+
audience: config.audience,
|
|
22
|
+
};
|
|
23
|
+
return jsonwebtoken_1.default.sign({ ...payload, tokenType: 'refresh' }, config.secret, options);
|
|
24
|
+
};
|
|
25
|
+
exports.generateRefreshToken = generateRefreshToken;
|
|
26
|
+
const verifyToken = (config, token) => {
|
|
31
27
|
try {
|
|
32
|
-
|
|
33
|
-
issuer:
|
|
34
|
-
audience:
|
|
28
|
+
return jsonwebtoken_1.default.verify(token, config.secret, {
|
|
29
|
+
issuer: config.issuer,
|
|
30
|
+
audience: config.audience,
|
|
35
31
|
});
|
|
36
|
-
return decoded;
|
|
37
32
|
}
|
|
38
|
-
catch
|
|
33
|
+
catch {
|
|
39
34
|
throw new Error('Invalid or expired token');
|
|
40
35
|
}
|
|
41
36
|
};
|
|
42
37
|
exports.verifyToken = verifyToken;
|
|
43
|
-
const generateRefreshToken = (JWT_SECRET, JWT_EXPIRES_IN, user) => {
|
|
44
|
-
const payload = {
|
|
45
|
-
id: user.id,
|
|
46
|
-
email: user.email,
|
|
47
|
-
phoneNumber: user.phoneNumber,
|
|
48
|
-
firstName: user.firstName,
|
|
49
|
-
lastName: user.lastName,
|
|
50
|
-
accountType: user.accountType,
|
|
51
|
-
is_active: user.is_active,
|
|
52
|
-
emailVerified: user.emailVerified,
|
|
53
|
-
phoneVerified: user.phoneVerified,
|
|
54
|
-
tokenVersion: user.tokenVersion ?? 0,
|
|
55
|
-
tokenType: 'refresh',
|
|
56
|
-
};
|
|
57
|
-
const options = {
|
|
58
|
-
expiresIn: JWT_EXPIRES_IN,
|
|
59
|
-
issuer: 'ggcoach-auth-service',
|
|
60
|
-
audience: 'ggcoach-app'
|
|
61
|
-
};
|
|
62
|
-
return jsonwebtoken_1.default.sign(payload, JWT_SECRET, options);
|
|
63
|
-
};
|
|
64
|
-
exports.generateRefreshToken = generateRefreshToken;
|
|
65
38
|
//# sourceMappingURL=jwt.js.map
|
package/dist/utils/jwt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../utils/jwt.ts"],"names":[],"mappings":";;;;;;AAAA,gEAA+B;
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../utils/jwt.ts"],"names":[],"mappings":";;;;;;AAAA,gEAA+B;AAGxB,MAAM,aAAa,GAAG,CAA2B,MAAiB,EAAE,OAAU,EAAU,EAAE;IAC/F,MAAM,OAAO,GAAoB;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KACP,CAAC;IAErB,OAAO,sBAAG,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/E,CAAC,CAAC;AARW,QAAA,aAAa,iBAQxB;AAEK,MAAM,oBAAoB,GAAG,CAA2B,MAAiB,EAAE,OAAU,EAAU,EAAE;IACtG,MAAM,OAAO,GAAoB;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KACP,CAAC;IAErB,OAAO,sBAAG,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChF,CAAC,CAAC;AARW,QAAA,oBAAoB,wBAQ/B;AAEK,MAAM,WAAW,GAAG,CAA2B,MAAiB,EAAE,KAAa,EAAK,EAAE;IAC3F,IAAI,CAAC;QACH,OAAO,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;YACtC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAM,CAAC;IACV,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC,CAAC;AATW,QAAA,WAAW,eAStB"}
|