honertia 0.1.21 → 0.1.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.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Error Context Capture for Honertia
3
+ *
4
+ * Utilities for capturing request, route, and handler context
5
+ * when errors occur.
6
+ */
7
+ import type { Context as HonoContext, Env } from 'hono';
8
+ import type { ErrorContext, SourceLocation, CodeSnippet } from './error-types.js';
9
+ /**
10
+ * Capture error context from a Hono request context.
11
+ *
12
+ * Extracts route information (method, path, params) and request details
13
+ * (URL, safe headers) for inclusion in structured errors. Sensitive headers
14
+ * like Authorization and Cookie are automatically filtered out.
15
+ *
16
+ * @param c - The Hono request context.
17
+ * @returns An ErrorContext object with route and request information.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * app.use('*', async (c, next) => {
22
+ * try {
23
+ * await next()
24
+ * } catch (err) {
25
+ * const context = captureErrorContext(c)
26
+ * const structured = toStructuredError(err, context)
27
+ * // ...
28
+ * }
29
+ * })
30
+ * ```
31
+ */
32
+ export declare function captureErrorContext<E extends Env>(c: HonoContext<E>): ErrorContext;
33
+ /**
34
+ * Parse a V8 stack trace into structured frames.
35
+ */
36
+ export interface StackFrame {
37
+ /** Function name if available */
38
+ functionName?: string;
39
+ /** File path */
40
+ file: string;
41
+ /** Line number */
42
+ line: number;
43
+ /** Column number */
44
+ column: number;
45
+ /** Whether this is from node_modules or framework code */
46
+ isInternal: boolean;
47
+ }
48
+ /**
49
+ * Parse a stack trace string into frames.
50
+ */
51
+ export declare function parseStackTrace(stack: string): StackFrame[];
52
+ /**
53
+ * Find the first user code frame (non-internal).
54
+ */
55
+ export declare function findUserFrame(frames: StackFrame[]): StackFrame | undefined;
56
+ /**
57
+ * Create a source location from an error.
58
+ * Attempts to find the relevant user code location.
59
+ */
60
+ export declare function createSourceLocation(error: Error): SourceLocation | undefined;
61
+ /**
62
+ * Create a code snippet from source code.
63
+ * Used when source code is available (e.g., in development).
64
+ *
65
+ * @param sourceCode - The full source code content.
66
+ * @param line - The 1-indexed line number where the error occurred.
67
+ * @param contextLines - Number of lines to include before/after the error line.
68
+ * @param highlightStart - Optional start column for highlighting.
69
+ * @param highlightEnd - Optional end column for highlighting.
70
+ * @returns A CodeSnippet with before/after context.
71
+ */
72
+ export declare function createCodeSnippet(sourceCode: string, line: number, contextLines?: number, highlightStart?: number, highlightEnd?: number): CodeSnippet;
73
+ /**
74
+ * Enhanced error context with source location.
75
+ */
76
+ export interface EnhancedErrorContext extends ErrorContext {
77
+ source?: SourceLocation;
78
+ }
79
+ /**
80
+ * Capture enhanced error context including source location.
81
+ */
82
+ export declare function captureEnhancedContext<E extends Env>(c: HonoContext<E>, error?: Error): EnhancedErrorContext;
83
+ /**
84
+ * Add handler context to an error context.
85
+ */
86
+ export declare function withHandlerContext(context: ErrorContext, file?: string, functionName?: string): ErrorContext;
87
+ /**
88
+ * Add service context to an error context.
89
+ */
90
+ export declare function withServiceContext(context: ErrorContext, serviceName: string, operation?: string): ErrorContext;
91
+ /**
92
+ * Merge error contexts, with later values taking precedence.
93
+ */
94
+ export declare function mergeContexts(...contexts: Partial<ErrorContext>[]): ErrorContext;
95
+ /**
96
+ * Create an empty error context.
97
+ */
98
+ export declare function emptyContext(): ErrorContext;
99
+ //# sourceMappingURL=error-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-context.d.ts","sourceRoot":"","sources":["../../src/effect/error-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAmCjF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,YAAY,CA4BlF;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,UAAU,EAAE,OAAO,CAAA;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CA2B3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,GAAG,SAAS,CAE1E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,cAAc,GAAG,SAAS,CAc7E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAU,EACxB,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,GACpB,WAAW,CA0Bb;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,GAAG,EAClD,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACjB,KAAK,CAAC,EAAE,KAAK,GACZ,oBAAoB,CAQtB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EACrB,IAAI,CAAC,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,YAAY,CAQd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,YAAY,CAQd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,GAAG,YAAY,CAmBhF;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAE3C"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Error Context Capture for Honertia
3
+ *
4
+ * Utilities for capturing request, route, and handler context
5
+ * when errors occur.
6
+ */
7
+ /**
8
+ * Headers that are safe to include in error context.
9
+ * Excludes sensitive headers like Authorization, Cookie, etc.
10
+ */
11
+ const SAFE_HEADERS = [
12
+ 'accept',
13
+ 'accept-language',
14
+ 'content-type',
15
+ 'content-length',
16
+ 'x-inertia',
17
+ 'x-inertia-version',
18
+ 'x-requested-with',
19
+ 'user-agent',
20
+ 'referer',
21
+ 'origin',
22
+ ];
23
+ /**
24
+ * Extract safe headers from a request.
25
+ */
26
+ function extractSafeHeaders(headers) {
27
+ const result = {};
28
+ for (const key of SAFE_HEADERS) {
29
+ const value = headers.get(key);
30
+ if (value) {
31
+ result[key] = value;
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ /**
37
+ * Capture error context from a Hono request context.
38
+ *
39
+ * Extracts route information (method, path, params) and request details
40
+ * (URL, safe headers) for inclusion in structured errors. Sensitive headers
41
+ * like Authorization and Cookie are automatically filtered out.
42
+ *
43
+ * @param c - The Hono request context.
44
+ * @returns An ErrorContext object with route and request information.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * app.use('*', async (c, next) => {
49
+ * try {
50
+ * await next()
51
+ * } catch (err) {
52
+ * const context = captureErrorContext(c)
53
+ * const structured = toStructuredError(err, context)
54
+ * // ...
55
+ * }
56
+ * })
57
+ * ```
58
+ */
59
+ export function captureErrorContext(c) {
60
+ const context = {};
61
+ // Route context
62
+ try {
63
+ const routePath = c.req.routePath ?? c.req.path;
64
+ const params = c.req.param();
65
+ context.route = {
66
+ method: c.req.method,
67
+ path: routePath,
68
+ params: typeof params === 'string' ? {} : params,
69
+ };
70
+ }
71
+ catch {
72
+ // Ignore errors extracting route info
73
+ }
74
+ // Request context
75
+ try {
76
+ context.request = {
77
+ url: c.req.url,
78
+ headers: extractSafeHeaders(c.req.raw.headers),
79
+ };
80
+ }
81
+ catch {
82
+ // Ignore errors extracting request info
83
+ }
84
+ return context;
85
+ }
86
+ /**
87
+ * Parse a stack trace string into frames.
88
+ */
89
+ export function parseStackTrace(stack) {
90
+ const frames = [];
91
+ const lines = stack.split('\n');
92
+ for (const line of lines) {
93
+ // Match V8 stack format: " at functionName (file:line:col)"
94
+ // or " at file:line:col"
95
+ const match = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+):(\d+):(\d+)\)?$/);
96
+ if (match) {
97
+ const file = match[2];
98
+ const isInternal = file.includes('node_modules') ||
99
+ file.includes('honertia/dist') ||
100
+ file.includes('effect/dist') ||
101
+ file.startsWith('node:');
102
+ frames.push({
103
+ functionName: match[1],
104
+ file,
105
+ line: parseInt(match[3], 10),
106
+ column: parseInt(match[4], 10),
107
+ isInternal,
108
+ });
109
+ }
110
+ }
111
+ return frames;
112
+ }
113
+ /**
114
+ * Find the first user code frame (non-internal).
115
+ */
116
+ export function findUserFrame(frames) {
117
+ return frames.find((f) => !f.isInternal);
118
+ }
119
+ /**
120
+ * Create a source location from an error.
121
+ * Attempts to find the relevant user code location.
122
+ */
123
+ export function createSourceLocation(error) {
124
+ if (!error.stack)
125
+ return undefined;
126
+ const frames = parseStackTrace(error.stack);
127
+ const userFrame = findUserFrame(frames);
128
+ if (!userFrame)
129
+ return undefined;
130
+ return {
131
+ file: userFrame.file,
132
+ line: userFrame.line,
133
+ column: userFrame.column,
134
+ functionName: userFrame.functionName,
135
+ };
136
+ }
137
+ /**
138
+ * Create a code snippet from source code.
139
+ * Used when source code is available (e.g., in development).
140
+ *
141
+ * @param sourceCode - The full source code content.
142
+ * @param line - The 1-indexed line number where the error occurred.
143
+ * @param contextLines - Number of lines to include before/after the error line.
144
+ * @param highlightStart - Optional start column for highlighting.
145
+ * @param highlightEnd - Optional end column for highlighting.
146
+ * @returns A CodeSnippet with before/after context.
147
+ */
148
+ export function createCodeSnippet(sourceCode, line, contextLines = 2, highlightStart, highlightEnd) {
149
+ const lines = sourceCode.split('\n');
150
+ // Validate line number is within bounds
151
+ const validLine = Math.max(1, Math.min(line, lines.length));
152
+ const startLine = Math.max(0, validLine - 1 - contextLines);
153
+ const endLine = Math.min(lines.length, validLine + contextLines);
154
+ const before = lines.slice(startLine, validLine - 1);
155
+ const errorLine = lines[validLine - 1] ?? '';
156
+ const after = lines.slice(validLine, endLine);
157
+ const snippet = {
158
+ before,
159
+ line: errorLine,
160
+ after,
161
+ };
162
+ if (highlightStart !== undefined && highlightEnd !== undefined) {
163
+ snippet.highlight = {
164
+ start: highlightStart,
165
+ end: highlightEnd,
166
+ };
167
+ }
168
+ return snippet;
169
+ }
170
+ /**
171
+ * Capture enhanced error context including source location.
172
+ */
173
+ export function captureEnhancedContext(c, error) {
174
+ const context = captureErrorContext(c);
175
+ if (error) {
176
+ context.source = createSourceLocation(error);
177
+ }
178
+ return context;
179
+ }
180
+ /**
181
+ * Add handler context to an error context.
182
+ */
183
+ export function withHandlerContext(context, file, functionName) {
184
+ return {
185
+ ...context,
186
+ handler: {
187
+ file,
188
+ function: functionName,
189
+ },
190
+ };
191
+ }
192
+ /**
193
+ * Add service context to an error context.
194
+ */
195
+ export function withServiceContext(context, serviceName, operation) {
196
+ return {
197
+ ...context,
198
+ service: {
199
+ name: serviceName,
200
+ operation,
201
+ },
202
+ };
203
+ }
204
+ /**
205
+ * Merge error contexts, with later values taking precedence.
206
+ */
207
+ export function mergeContexts(...contexts) {
208
+ const result = {};
209
+ for (const ctx of contexts) {
210
+ if (ctx.route) {
211
+ result.route = { ...result.route, ...ctx.route };
212
+ }
213
+ if (ctx.handler) {
214
+ result.handler = { ...result.handler, ...ctx.handler };
215
+ }
216
+ if (ctx.request) {
217
+ result.request = { ...result.request, ...ctx.request };
218
+ }
219
+ if (ctx.service) {
220
+ result.service = { ...result.service, ...ctx.service };
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+ /**
226
+ * Create an empty error context.
227
+ */
228
+ export function emptyContext() {
229
+ return {};
230
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Error Formatters for Honertia
3
+ *
4
+ * Multiple output formats for structured errors:
5
+ * - JSON for AI/API consumption
6
+ * - Terminal with ANSI colors for CLI
7
+ * - Inertia for browser rendering
8
+ */
9
+ import type { HonertiaStructuredError } from './error-types.js';
10
+ /**
11
+ * Interface for error formatters.
12
+ */
13
+ export interface ErrorFormatter {
14
+ format(error: HonertiaStructuredError): string | object;
15
+ }
16
+ /**
17
+ * Options for JSON formatter.
18
+ */
19
+ export interface JsonFormatterOptions {
20
+ /** Pretty print with indentation */
21
+ pretty?: boolean;
22
+ /** Include source location */
23
+ includeSource?: boolean;
24
+ /** Include request/route context */
25
+ includeContext?: boolean;
26
+ /** Include fix suggestions */
27
+ includeFixes?: boolean;
28
+ /** Include documentation links */
29
+ includeDocs?: boolean;
30
+ }
31
+ /**
32
+ * JSON formatter for AI/LLM and API consumption.
33
+ * Outputs machine-readable structured error data.
34
+ */
35
+ export declare class JsonErrorFormatter implements ErrorFormatter {
36
+ private options;
37
+ constructor(options?: JsonFormatterOptions);
38
+ format(error: HonertiaStructuredError): object;
39
+ /**
40
+ * Format to JSON string.
41
+ */
42
+ formatString(error: HonertiaStructuredError): string;
43
+ }
44
+ /**
45
+ * Options for terminal formatter.
46
+ */
47
+ export interface TerminalFormatterOptions {
48
+ /** Use ANSI colors */
49
+ useColors?: boolean;
50
+ /** Show source code snippet */
51
+ showSnippet?: boolean;
52
+ /** Show fix suggestions */
53
+ showFixes?: boolean;
54
+ /** Maximum number of fixes to show */
55
+ maxFixes?: number;
56
+ }
57
+ /**
58
+ * Terminal formatter with ANSI colors.
59
+ * Outputs Rust/Elm-style human-readable errors.
60
+ */
61
+ export declare class TerminalErrorFormatter implements ErrorFormatter {
62
+ private options;
63
+ private c;
64
+ constructor(options?: TerminalFormatterOptions);
65
+ format(error: HonertiaStructuredError): string;
66
+ }
67
+ /**
68
+ * Options for Inertia formatter.
69
+ */
70
+ export interface InertiaFormatterOptions {
71
+ /** Include fix suggestions */
72
+ includeFixes?: boolean;
73
+ /** Include source location */
74
+ includeSource?: boolean;
75
+ /** Development mode (shows more details) */
76
+ isDev?: boolean;
77
+ }
78
+ /**
79
+ * Inertia formatter for browser error pages.
80
+ * Outputs props for an Inertia error component.
81
+ */
82
+ export declare class InertiaErrorFormatter implements ErrorFormatter {
83
+ private options;
84
+ constructor(options?: InertiaFormatterOptions);
85
+ format(error: HonertiaStructuredError): object;
86
+ /**
87
+ * Get a safe message for production (no sensitive details).
88
+ */
89
+ private getSafeMessage;
90
+ }
91
+ /**
92
+ * Output format types.
93
+ */
94
+ export type OutputFormat = 'json' | 'terminal' | 'inertia';
95
+ /**
96
+ * Request context for format detection.
97
+ */
98
+ export interface FormatDetectionContext {
99
+ header: (name: string) => string | undefined;
100
+ method: string;
101
+ url: string;
102
+ }
103
+ /**
104
+ * Detect the appropriate output format based on request headers and environment.
105
+ *
106
+ * Detection priority:
107
+ * 1. AI/CLI User-Agents (claude-code, curl) → 'json'
108
+ * 2. Accept: application/json header → 'json'
109
+ * 3. X-Inertia: true header → 'inertia'
110
+ * 4. Content-Type: application/json → 'json'
111
+ * 5. Development environment → 'terminal'
112
+ * 6. Production browser requests → 'inertia'
113
+ *
114
+ * @param request - Request context with header accessor, method, and URL.
115
+ * @param env - Environment variables for detecting dev/prod mode.
116
+ * @returns The detected output format: 'json', 'terminal', or 'inertia'.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const format = detectOutputFormat(
121
+ * { header: (n) => c.req.header(n), method: c.req.method, url: c.req.url },
122
+ * c.env
123
+ * )
124
+ * const formatter = createFormatter(format, isDev)
125
+ * ```
126
+ */
127
+ export declare function detectOutputFormat(request: FormatDetectionContext, env?: Record<string, unknown>): OutputFormat;
128
+ /**
129
+ * Create a formatter instance for the given output format.
130
+ *
131
+ * @param format - The output format: 'json', 'terminal', or 'inertia'.
132
+ * @param isDev - Whether to include dev-only details (source, context). Defaults to true.
133
+ * @returns An ErrorFormatter instance configured for the format.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const format = detectOutputFormat(request, env)
138
+ * const formatter = createFormatter(format, isDev)
139
+ * const output = formatter.format(structuredError)
140
+ * ```
141
+ */
142
+ export declare function createFormatter(format: OutputFormat, isDev?: boolean): ErrorFormatter;
143
+ //# sourceMappingURL=error-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-formatter.d.ts","sourceRoot":"","sources":["../../src/effect/error-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAkB,MAAM,kBAAkB,CAAA;AAE/E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,GAAG,MAAM,CAAA;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,oCAAoC;IACpC,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,8BAA8B;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAC3C,OAAO,CAAC,OAAO;gBAAP,OAAO,GAAE,oBAAyB;IAYtD,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;IA+C9C;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;CAIrD;AAoBD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,sBAAsB;IACtB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2BAA2B;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAG/C,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,CAAC,CAAqD;gBAE1C,OAAO,GAAE,wBAA6B;IAkB1D,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;CAoH/C;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,8BAA8B;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAC9C,OAAO,CAAC,OAAO;gBAAP,OAAO,GAAE,uBAA4B;IASzD,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;IAqC9C;;OAEG;IACH,OAAO,CAAC,cAAc;CAYvB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;AAE1D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,sBAAsB,EAC/B,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,YAAY,CA0Cd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,YAAY,EACpB,KAAK,GAAE,OAAc,GACpB,cAAc,CA0BhB"}