tpaga-logger 0.0.3 → 0.0.4
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/dist/index.cjs +21 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +21 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -80,20 +80,28 @@ var resolveCorrelationId = (headers = {}) => {
|
|
|
80
80
|
return h["x-correlation-id"] ?? h["x-request-id"] ?? (0, import_crypto.randomUUID)();
|
|
81
81
|
};
|
|
82
82
|
var EXPRESS_LOG_KEY = "tpagaLog";
|
|
83
|
-
var withTpagaExpressLogger = (logger) =>
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
var withTpagaExpressLogger = (logger) => {
|
|
84
|
+
const requestMiddleware = (req, res, next) => {
|
|
85
|
+
const start = Date.now();
|
|
86
|
+
const correlationId = resolveCorrelationId(req.headers);
|
|
87
|
+
const log = logger.startOperation(req.path, { correlationId, method: req.method });
|
|
88
|
+
req.log = log;
|
|
89
|
+
res.log = log;
|
|
90
|
+
res.locals[EXPRESS_LOG_KEY] = log;
|
|
91
|
+
res.on("finish", () => {
|
|
92
|
+
log.emit("info", "request completed", {
|
|
93
|
+
outcome: res.statusCode < 400 ? "success" : "error",
|
|
94
|
+
durationMs: Date.now() - start,
|
|
95
|
+
statusCode: res.statusCode
|
|
96
|
+
});
|
|
94
97
|
});
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
next();
|
|
99
|
+
};
|
|
100
|
+
const errorMiddleware = (err, req, _res, next) => {
|
|
101
|
+
req.log.with({ error: serializeError(err) });
|
|
102
|
+
next(err);
|
|
103
|
+
};
|
|
104
|
+
return [requestMiddleware, errorMiddleware];
|
|
97
105
|
};
|
|
98
106
|
var getLog = (res) => res.locals[EXPRESS_LOG_KEY];
|
|
99
107
|
var withLogger = (logger, operationName, handler) => async (event, ..._rest) => {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger, withTpagaExpressLogger, getLog } from './logger.js';\nexport { LOG_LEVELS, OUTCOMES } from './constants.js';\nexport type {\n Logger,\n LoggerConfig,\n LogLevel,\n Outcome,\n SerializedError,\n TerminalFields,\n WideEventBuilder,\n} from './types.js';\n","import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/constants.ts"],"sourcesContent":["export { createLogger, serializeError, withLogger, withTpagaExpressLogger, getLog } from './logger.js';\nexport { LOG_LEVELS, OUTCOMES } from './constants.js';\nexport type {\n Logger,\n LoggerConfig,\n LogLevel,\n Outcome,\n SerializedError,\n TerminalFields,\n WideEventBuilder,\n} from './types.js';\n","import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\ntype RequestMiddleware = (req: Request, res: Response, next: NextFunction) => void;\ntype ErrorMiddleware = (err: unknown, req: Request, res: Response, next: NextFunction) => void;\n\nexport const withTpagaExpressLogger = (logger: Logger): [RequestMiddleware, ErrorMiddleware] => {\n const requestMiddleware: RequestMiddleware = (req, res, next) => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\n const errorMiddleware: ErrorMiddleware = (err, req, _res, next) => {\n req.log.with({ error: serializeError(err) });\n next(err);\n };\n\n return [requestMiddleware, errorMiddleware];\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":"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","names":["pino"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -35,6 +35,9 @@ declare global {
|
|
|
35
35
|
interface Request {
|
|
36
36
|
log: WideEventBuilder;
|
|
37
37
|
}
|
|
38
|
+
interface Response {
|
|
39
|
+
log: WideEventBuilder;
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
declare const serializeError: (err: unknown) => SerializedError;
|
|
@@ -43,7 +46,9 @@ type EventWithHeaders = {
|
|
|
43
46
|
headers?: Record<string, string | undefined>;
|
|
44
47
|
};
|
|
45
48
|
type HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;
|
|
46
|
-
|
|
49
|
+
type RequestMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
50
|
+
type ErrorMiddleware = (err: unknown, req: Request, res: Response, next: NextFunction) => void;
|
|
51
|
+
declare const withTpagaExpressLogger: (logger: Logger) => [RequestMiddleware, ErrorMiddleware];
|
|
47
52
|
declare const getLog: (res: Response) => WideEventBuilder;
|
|
48
53
|
declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
|
|
49
54
|
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ declare global {
|
|
|
35
35
|
interface Request {
|
|
36
36
|
log: WideEventBuilder;
|
|
37
37
|
}
|
|
38
|
+
interface Response {
|
|
39
|
+
log: WideEventBuilder;
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
declare const serializeError: (err: unknown) => SerializedError;
|
|
@@ -43,7 +46,9 @@ type EventWithHeaders = {
|
|
|
43
46
|
headers?: Record<string, string | undefined>;
|
|
44
47
|
};
|
|
45
48
|
type HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;
|
|
46
|
-
|
|
49
|
+
type RequestMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
50
|
+
type ErrorMiddleware = (err: unknown, req: Request, res: Response, next: NextFunction) => void;
|
|
51
|
+
declare const withTpagaExpressLogger: (logger: Logger) => [RequestMiddleware, ErrorMiddleware];
|
|
47
52
|
declare const getLog: (res: Response) => WideEventBuilder;
|
|
48
53
|
declare const withLogger: <TEvent extends EventWithHeaders>(logger: Logger, operationName: string, handler: HandlerWithBuilder<TEvent>) => (event: TEvent, ..._rest: unknown[]) => Promise<unknown>;
|
|
49
54
|
|
package/dist/index.js
CHANGED
|
@@ -38,20 +38,28 @@ var resolveCorrelationId = (headers = {}) => {
|
|
|
38
38
|
return h["x-correlation-id"] ?? h["x-request-id"] ?? randomUUID();
|
|
39
39
|
};
|
|
40
40
|
var EXPRESS_LOG_KEY = "tpagaLog";
|
|
41
|
-
var withTpagaExpressLogger = (logger) =>
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
var withTpagaExpressLogger = (logger) => {
|
|
42
|
+
const requestMiddleware = (req, res, next) => {
|
|
43
|
+
const start = Date.now();
|
|
44
|
+
const correlationId = resolveCorrelationId(req.headers);
|
|
45
|
+
const log = logger.startOperation(req.path, { correlationId, method: req.method });
|
|
46
|
+
req.log = log;
|
|
47
|
+
res.log = log;
|
|
48
|
+
res.locals[EXPRESS_LOG_KEY] = log;
|
|
49
|
+
res.on("finish", () => {
|
|
50
|
+
log.emit("info", "request completed", {
|
|
51
|
+
outcome: res.statusCode < 400 ? "success" : "error",
|
|
52
|
+
durationMs: Date.now() - start,
|
|
53
|
+
statusCode: res.statusCode
|
|
54
|
+
});
|
|
52
55
|
});
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
const errorMiddleware = (err, req, _res, next) => {
|
|
59
|
+
req.log.with({ error: serializeError(err) });
|
|
60
|
+
next(err);
|
|
61
|
+
};
|
|
62
|
+
return [requestMiddleware, errorMiddleware];
|
|
55
63
|
};
|
|
56
64
|
var getLog = (res) => res.locals[EXPRESS_LOG_KEY];
|
|
57
65
|
var withLogger = (logger, operationName, handler) => async (event, ..._rest) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/logger.ts","../src/constants.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/constants.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport pino from 'pino';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Logger, LoggerConfig, SerializedError, TerminalFields, WideEventBuilder } from './types.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n log: WideEventBuilder;\n }\n interface Response {\n log: WideEventBuilder;\n }\n }\n}\n\nexport const serializeError = (err: unknown): SerializedError =>\n err instanceof Error\n ? {\n type: err.name,\n message: err.message,\n code: (err as { code?: string }).code,\n stack: err.stack,\n }\n : { type: 'UnknownError', message: String(err) };\n\nconst createWideEventBuilder = (\n base: Record<string, unknown>,\n log: pino.Logger,\n): WideEventBuilder => {\n const ctx = { ...base };\n return {\n with: (fields) => Object.assign(ctx, fields),\n emit: (level, message, terminal: TerminalFields) => log[level]({ ...ctx, ...terminal }, message),\n };\n};\n\nexport const createLogger = (config: LoggerConfig): Logger => {\n const pretty = process.env.LOG_PRETTY === 'true';\n const log = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n base: { service: config.service, environment: config.environment },\n formatters: {\n level: (label) => ({ level: label }),\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: {\n paths: (config.redactKeys as string[]) ?? [],\n censor: '[REDACTED]',\n },\n ...(pretty ? { transport: { target: 'pino-pretty', options: { colorize: true } } } : {}),\n });\n\n return {\n startOperation: (functionName, base = {}) =>\n createWideEventBuilder({ function: functionName, ...base }, log),\n };\n};\n\ntype EventWithHeaders = { headers?: Record<string, string | undefined> };\ntype HandlerWithBuilder<TEvent> = (event: TEvent, builder: WideEventBuilder) => Promise<unknown>;\n\nconst resolveCorrelationId = (headers: Record<string, string | undefined> = {}): string => {\n const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));\n return h['x-correlation-id'] ?? h['x-request-id'] ?? randomUUID();\n};\n\nconst EXPRESS_LOG_KEY = 'tpagaLog';\n\ntype RequestMiddleware = (req: Request, res: Response, next: NextFunction) => void;\ntype ErrorMiddleware = (err: unknown, req: Request, res: Response, next: NextFunction) => void;\n\nexport const withTpagaExpressLogger = (logger: Logger): [RequestMiddleware, ErrorMiddleware] => {\n const requestMiddleware: RequestMiddleware = (req, res, next) => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(req.headers as Record<string, string | undefined>);\n const log = logger.startOperation(req.path, { correlationId, method: req.method });\n\n req.log = log;\n res.log = log;\n res.locals[EXPRESS_LOG_KEY] = log;\n\n res.on('finish', () => {\n log.emit('info', 'request completed', {\n outcome: res.statusCode < 400 ? 'success' : 'error',\n durationMs: Date.now() - start,\n statusCode: res.statusCode,\n });\n });\n\n next();\n };\n\n const errorMiddleware: ErrorMiddleware = (err, req, _res, next) => {\n req.log.with({ error: serializeError(err) });\n next(err);\n };\n\n return [requestMiddleware, errorMiddleware];\n};\n\nexport const getLog = (res: Response): WideEventBuilder =>\n res.locals[EXPRESS_LOG_KEY] as WideEventBuilder;\n\nexport const withLogger = <TEvent extends EventWithHeaders>(\n logger: Logger,\n operationName: string,\n handler: HandlerWithBuilder<TEvent>,\n) =>\n async (event: TEvent, ..._rest: unknown[]): Promise<unknown> => {\n const start = Date.now();\n const correlationId = resolveCorrelationId(event.headers);\n const builder = logger.startOperation(operationName, { correlationId });\n\n try {\n const result = await handler(event, builder);\n const statusCode =\n result != null &&\n typeof result === 'object' &&\n 'statusCode' in result &&\n typeof (result as { statusCode: unknown }).statusCode === 'number'\n ? (result as { statusCode: number }).statusCode\n : undefined;\n builder.emit('info', `${operationName} completed`, {\n outcome: 'success',\n durationMs: Date.now() - start,\n ...(statusCode !== undefined ? { statusCode } : {}),\n });\n return result;\n } catch (err) {\n builder.emit('error', `${operationName} failed`, {\n outcome: 'error',\n durationMs: Date.now() - start,\n error: serializeError(err),\n });\n throw err;\n }\n };\n","export const LOG_LEVELS = ['info', 'warn', 'error', 'debug'] as const;\n\nexport const OUTCOMES = [\n 'success',\n 'error',\n 'validation_failed',\n 'not_found',\n 'conflict',\n 'timeout',\n 'skipped',\n] as const;\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AAeV,IAAM,iBAAiB,CAAC,QAC7B,eAAe,QACX;AAAA,EACE,MAAM,IAAI;AAAA,EACV,SAAS,IAAI;AAAA,EACb,MAAO,IAA0B;AAAA,EACjC,OAAO,IAAI;AACb,IACA,EAAE,MAAM,gBAAgB,SAAS,OAAO,GAAG,EAAE;AAEnD,IAAM,yBAAyB,CAC7B,MACA,QACqB;AACrB,QAAM,MAAM,EAAE,GAAG,KAAK;AACtB,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,OAAO,OAAO,KAAK,MAAM;AAAA,IAC3C,MAAM,CAAC,OAAO,SAAS,aAA6B,IAAI,KAAK,EAAE,EAAE,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO;AAAA,EACjG;AACF;AAEO,IAAM,eAAe,CAAC,WAAiC;AAC5D,QAAM,SAAS,QAAQ,IAAI,eAAe;AAC1C,QAAM,MAAM,KAAK;AAAA,IACf,OAAO,QAAQ,IAAI,aAAa;AAAA,IAChC,MAAM,EAAE,SAAS,OAAO,SAAS,aAAa,OAAO,YAAY;AAAA,IACjE,YAAY;AAAA,MACV,OAAO,CAAC,WAAW,EAAE,OAAO,MAAM;AAAA,IACpC;AAAA,IACA,WAAW,KAAK,iBAAiB;AAAA,IACjC,QAAQ;AAAA,MACN,OAAQ,OAAO,cAA2B,CAAC;AAAA,MAC3C,QAAQ;AAAA,IACV;AAAA,IACA,GAAI,SAAS,EAAE,WAAW,EAAE,QAAQ,eAAe,SAAS,EAAE,UAAU,KAAK,EAAE,EAAE,IAAI,CAAC;AAAA,EACxF,CAAC;AAED,SAAO;AAAA,IACL,gBAAgB,CAAC,cAAc,OAAO,CAAC,MACrC,uBAAuB,EAAE,UAAU,cAAc,GAAG,KAAK,GAAG,GAAG;AAAA,EACnE;AACF;AAKA,IAAM,uBAAuB,CAAC,UAA8C,CAAC,MAAc;AACzF,QAAM,IAAI,OAAO,YAAY,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;AAC1F,SAAO,EAAE,kBAAkB,KAAK,EAAE,cAAc,KAAK,WAAW;AAClE;AAEA,IAAM,kBAAkB;AAKjB,IAAM,yBAAyB,CAAC,WAAyD;AAC9F,QAAM,oBAAuC,CAAC,KAAK,KAAK,SAAS;AAC/D,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,gBAAgB,qBAAqB,IAAI,OAA6C;AAC5F,UAAM,MAAM,OAAO,eAAe,IAAI,MAAM,EAAE,eAAe,QAAQ,IAAI,OAAO,CAAC;AAEjF,QAAI,MAAM;AACV,QAAI,MAAM;AACV,QAAI,OAAO,eAAe,IAAI;AAE9B,QAAI,GAAG,UAAU,MAAM;AACrB,UAAI,KAAK,QAAQ,qBAAqB;AAAA,QACpC,SAAS,IAAI,aAAa,MAAM,YAAY;AAAA,QAC5C,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,YAAY,IAAI;AAAA,MAClB,CAAC;AAAA,IACH,CAAC;AAED,SAAK;AAAA,EACP;AAEA,QAAM,kBAAmC,CAAC,KAAK,KAAK,MAAM,SAAS;AACjE,QAAI,IAAI,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,CAAC;AAC3C,SAAK,GAAG;AAAA,EACV;AAEA,SAAO,CAAC,mBAAmB,eAAe;AAC5C;AAEO,IAAM,SAAS,CAAC,QACrB,IAAI,OAAO,eAAe;AAErB,IAAM,aAAa,CACxB,QACA,eACA,YAEA,OAAO,UAAkB,UAAuC;AAC9D,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,gBAAgB,qBAAqB,MAAM,OAAO;AACxD,QAAM,UAAU,OAAO,eAAe,eAAe,EAAE,cAAc,CAAC;AAEtE,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,OAAO,OAAO;AAC3C,UAAM,aACJ,UAAU,QACV,OAAO,WAAW,YAClB,gBAAgB,UAChB,OAAQ,OAAmC,eAAe,WACrD,OAAkC,aACnC;AACN,YAAQ,KAAK,QAAQ,GAAG,aAAa,cAAc;AAAA,MACjD,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,IACnD,CAAC;AACD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,SAAS,GAAG,aAAa,WAAW;AAAA,MAC/C,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO,eAAe,GAAG;AAAA,IAC3B,CAAC;AACD,UAAM;AAAA,EACR;AACF;;;ACzIK,IAAM,aAAa,CAAC,QAAQ,QAAQ,SAAS,OAAO;AAEpD,IAAM,WAAW;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
|