@logtape/logtape 2.1.0-dev.550 → 2.1.0-dev.576
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/formatter.cjs +4 -1
- package/dist/formatter.d.cts.map +1 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +4 -1
- package/dist/formatter.js.map +1 -1
- package/dist/sink.cjs +3 -3
- package/dist/sink.js +3 -3
- package/dist/sink.js.map +1 -1
- package/package.json +1 -1
package/dist/formatter.cjs
CHANGED
|
@@ -452,7 +452,10 @@ function getJsonLinesFormatter(options = {}) {
|
|
|
452
452
|
if (isTemplateMessage) getMessage = (record) => {
|
|
453
453
|
if (typeof record.rawMessage === "string") return record.rawMessage;
|
|
454
454
|
let msg = "";
|
|
455
|
-
for (let i = 0; i < record.rawMessage.length; i++)
|
|
455
|
+
for (let i = 0; i < record.rawMessage.length; i++) {
|
|
456
|
+
if (i > 0) msg += "{}";
|
|
457
|
+
msg += record.rawMessage[i];
|
|
458
|
+
}
|
|
456
459
|
return msg;
|
|
457
460
|
};
|
|
458
461
|
else getMessage = (record) => {
|
package/dist/formatter.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.cts","names":[],"sources":["../src/formatter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA+DA;AA+BA;;;AA6IoB,KA3OR,aAAA,GA2OQ,CAAA,MAAA,EA3OiB,SA2OjB,EAAA,GAAA,MAAA;AAAe;AA+TnC;;;AAEG,UA7ec,eAAA,CA6ed;EAAa;AAiHhB;AAQA;EAyBY,SAAA,EAAA,MAAS,GAAA,IAAA;EA4BJ;;;EA0CW,KAKT,EAAA,MAAA;EAAS;;;EAiBc,QAA1B,EAAA,MAAA;EAAM;;;EAhEiD,OAAA,EAAA,MAAA;EAqFvD;;;EACyB,MACtC,EA1tBO,SA0tBP;AAAa;AAgEhB;AAMA;AAiEA;;AACW,UA31BM,oBAAA,CA21BN;EAA8B;AACzB;
|
|
1
|
+
{"version":3,"file":"formatter.d.cts","names":[],"sources":["../src/formatter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA+DA;AA+BA;;;AA6IoB,KA3OR,aAAA,GA2OQ,CAAA,MAAA,EA3OiB,SA2OjB,EAAA,GAAA,MAAA;AAAe;AA+TnC;;;AAEG,UA7ec,eAAA,CA6ed;EAAa;AAiHhB;AAQA;EAyBY,SAAA,EAAA,MAAS,GAAA,IAAA;EA4BJ;;;EA0CW,KAKT,EAAA,MAAA;EAAS;;;EAiBc,QAA1B,EAAA,MAAA;EAAM;;;EAhEiD,OAAA,EAAA,MAAA;EAqFvD;;;EACyB,MACtC,EA1tBO,SA0tBP;AAAa;AAgEhB;AAMA;AAiEA;;AACW,UA31BM,oBAAA,CA21BN;EAA8B;AACzB;AA8JhB;AAUA;AAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEA/8Be;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAmEK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+TJ,gBAAA,WACL,uBACR;;;;;;;;;;;cAiHU,sBAAsB;;;;;KAQvB,SAAA;;;;;KAyBA,SAAA;;;;;UA4BK,yBAAA,SAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0ChC;;;;mBAKA;;;;eAKJ;;;;;;;;;;;gBAYC,OAAO,UAAU;;;;kBAKf;;;;kBAKA;;;;;;;;;;iBAWF,qBAAA,WACL,4BACR;;;;;;;;;;cAgEU,oBAAoB;;;;;UAMhB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiED,qBAAA,WACL,4BACR;;;;;;;;;;;;;;;;;cA8JU,oBAAoB;;;;;;;;;KAUrB,gBAAA,YAA4B;;;;;;;;iBAqBxB,uBAAA,SAAgC"}
|
package/dist/formatter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../src/formatter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA+DA;AA+BA;;;AA6IoB,KA3OR,aAAA,GA2OQ,CAAA,MAAA,EA3OiB,SA2OjB,EAAA,GAAA,MAAA;AAAe;AA+TnC;;;AAEG,UA7ec,eAAA,CA6ed;EAAa;AAiHhB;AAQA;EAyBY,SAAA,EAAA,MAAS,GAAA,IAAA;EA4BJ;;;EA0CW,KAKT,EAAA,MAAA;EAAS;;;EAiBc,QAA1B,EAAA,MAAA;EAAM;;;EAhEiD,OAAA,EAAA,MAAA;EAqFvD;;;EACyB,MACtC,EA1tBO,SA0tBP;AAAa;AAgEhB;AAMA;AAiEA;;AACW,UA31BM,oBAAA,CA21BN;EAA8B;AACzB;
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../src/formatter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA+DA;AA+BA;;;AA6IoB,KA3OR,aAAA,GA2OQ,CAAA,MAAA,EA3OiB,SA2OjB,EAAA,GAAA,MAAA;AAAe;AA+TnC;;;AAEG,UA7ec,eAAA,CA6ed;EAAa;AAiHhB;AAQA;EAyBY,SAAA,EAAA,MAAS,GAAA,IAAA;EA4BJ;;;EA0CW,KAKT,EAAA,MAAA;EAAS;;;EAiBc,QAA1B,EAAA,MAAA;EAAM;;;EAhEiD,OAAA,EAAA,MAAA;EAqFvD;;;EACyB,MACtC,EA1tBO,SA0tBP;AAAa;AAgEhB;AAMA;AAiEA;;AACW,UA31BM,oBAAA,CA21BN;EAA8B;AACzB;AA8JhB;AAUA;AAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEA/8Be;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAmEK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+TJ,gBAAA,WACL,uBACR;;;;;;;;;;;cAiHU,sBAAsB;;;;;KAQvB,SAAA;;;;;KAyBA,SAAA;;;;;UA4BK,yBAAA,SAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0ChC;;;;mBAKA;;;;eAKJ;;;;;;;;;;;gBAYC,OAAO,UAAU;;;;kBAKf;;;;kBAKA;;;;;;;;;;iBAWF,qBAAA,WACL,4BACR;;;;;;;;;;cAgEU,oBAAoB;;;;;UAMhB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiED,qBAAA,WACL,4BACR;;;;;;;;;;;;;;;;;cA8JU,oBAAoB;;;;;;;;;KAUrB,gBAAA,YAA4B;;;;;;;;iBAqBxB,uBAAA,SAAgC"}
|
package/dist/formatter.js
CHANGED
|
@@ -451,7 +451,10 @@ function getJsonLinesFormatter(options = {}) {
|
|
|
451
451
|
if (isTemplateMessage) getMessage = (record) => {
|
|
452
452
|
if (typeof record.rawMessage === "string") return record.rawMessage;
|
|
453
453
|
let msg = "";
|
|
454
|
-
for (let i = 0; i < record.rawMessage.length; i++)
|
|
454
|
+
for (let i = 0; i < record.rawMessage.length; i++) {
|
|
455
|
+
if (i > 0) msg += "{}";
|
|
456
|
+
msg += record.rawMessage[i];
|
|
457
|
+
}
|
|
455
458
|
return msg;
|
|
456
459
|
};
|
|
457
460
|
else getMessage = (record) => {
|
package/dist/formatter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.js","names":["levelAbbreviations: Record<LogLevel, string>","inspect: (value: unknown, options?: { colors?: boolean }) => string","num: number","minutes: number","full: boolean","formatter: Intl.DateTimeFormat","ts: number","config: TimeZoneConfig","timeZone: string | null | undefined","pattern: TimestampPattern","timeZone: TimeZoneConfig","lineEnding?: \"lf\" | \"crlf\"","_key: string","value: unknown","serialized: Record<string, unknown>","options: TextFormatterOptions","v: unknown","level: LogLevel","formatter: (values: FormattedValues) => string","record: LogRecord","message: string","parts: string[]","values: FormattedValues","defaultTextFormatter: TextFormatter","ansiColors: Record<AnsiColor, string>","ansiStyles: Record<AnsiStyle, string>","defaultLevelColors: Record<LogLevel, AnsiColor | null>","options: AnsiColorFormatterOptions","ansiColorFormatter: TextFormatter","options: JsonLinesFormatterOptions","joinCategory: (category: readonly string[]) => string | readonly string[]","category: readonly string[]","getProperties: (\n properties: Record<string, unknown>,\n ) => Record<string, unknown>","result: Record<string, unknown>","getMessage: (record: LogRecord) => string","jsonLinesFormatter: TextFormatter","logLevelStyles: Record<LogLevel, string>","values: unknown[]"],"sources":["../src/formatter.ts"],"sourcesContent":["import * as util from \"#util\";\nimport type { LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A text formatter is a function that accepts a log record and returns\n * a string.\n *\n * @param record The log record to format.\n * @returns The formatted log record.\n */\nexport type TextFormatter = (record: LogRecord) => string;\n\n/**\n * The severity level abbreviations.\n */\nconst levelAbbreviations: Record<LogLevel, string> = {\n \"trace\": \"TRC\",\n \"debug\": \"DBG\",\n \"info\": \"INF\",\n \"warning\": \"WRN\",\n \"error\": \"ERR\",\n \"fatal\": \"FTL\",\n};\n\n/**\n * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},\n * and in Node.js/Bun it is `util.inspect()`. If neither is available, it\n * falls back to {@link JSON.stringify}.\n *\n * @param value The value to inspect.\n * @param options The options for inspecting the value.\n * If `colors` is `true`, the output will be ANSI-colored.\n * @returns The string representation of the value.\n */\nconst inspect: (value: unknown, options?: { colors?: boolean }) => string =\n // @ts-ignore: Browser detection\n // dnt-shim-ignore\n typeof document !== \"undefined\" ||\n // @ts-ignore: React Native detection\n // dnt-shim-ignore\n typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\"\n ? (v) => JSON.stringify(v)\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n : \"Deno\" in globalThis && \"inspect\" in globalThis.Deno &&\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n typeof globalThis.Deno.inspect === \"function\"\n ? (v, opts) =>\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n globalThis.Deno.inspect(v, {\n strAbbreviateSize: Infinity,\n iterableLimit: Infinity,\n ...opts,\n })\n // @ts-ignore: Node.js global\n // dnt-shim-ignore\n : util != null && \"inspect\" in util && typeof util.inspect === \"function\"\n ? (v, opts) =>\n // @ts-ignore: Node.js global\n // dnt-shim-ignore\n util.inspect(v, {\n maxArrayLength: Infinity,\n maxStringLength: Infinity,\n ...opts,\n })\n : (v) => JSON.stringify(v);\n\n/**\n * The formatted values for a log record.\n * @since 0.6.0\n */\nexport interface FormattedValues {\n /**\n * The formatted timestamp.\n */\n timestamp: string | null;\n\n /**\n * The formatted log level.\n */\n level: string;\n\n /**\n * The formatted category.\n */\n category: string;\n\n /**\n * The formatted message.\n */\n message: string;\n\n /**\n * The unformatted log record.\n */\n record: LogRecord;\n}\n\n/**\n * The various options for the built-in text formatters.\n * @since 0.6.0\n */\nexport interface TextFormatterOptions {\n /**\n * The timestamp format. This can be one of the following:\n *\n * - `\"date-time-timezone\"`: The date and time with the full timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00:00\"`).\n * - `\"date-time-tz\"`: The date and time with the short timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00\"`).\n * - `\"date-time\"`: The date and time without the timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000\"`).\n * - `\"time-timezone\"`: The time with the full timezone offset but without\n * the date (e.g., `\"22:13:20.000 +00:00\"`).\n * - `\"time-tz\"`: The time with the short timezone offset but without the date\n * (e.g., `\"22:13:20.000 +00\"`).\n * - `\"time\"`: The time without the date or timezone offset\n * (e.g., `\"22:13:20.000\"`).\n * - `\"date\"`: The date without the time or timezone offset\n * (e.g., `\"2023-11-14\"`).\n * - `\"rfc3339\"`: The date and time in RFC 3339 format\n * (e.g., `\"2023-11-14T22:13:20.000Z\"`).\n * - `\"none\"` or `\"disabled\"`: No display\n *\n * Alternatively, this can be a function that accepts a timestamp and returns\n * a string.\n *\n * The default is `\"date-time-timezone\"`.\n */\n timestamp?:\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\"\n | \"disabled\"\n | ((ts: number) => string | null);\n\n /**\n * The timezone used for timestamp rendering.\n *\n * - `undefined` (default): UTC (preserves existing behavior)\n * - `null`: System local timezone\n * - IANA timezone name such as `\"America/Bogota\"` or `\"Asia/Seoul\"`\n * - Fixed UTC offset string such as `\"+09:00\"` or `\"-05:00\"`\n *\n * @since 2.1.0\n */\n timeZone?: string | null;\n\n /**\n * The log level format. This can be one of the following:\n *\n * - `\"ABBR\"`: The log level abbreviation in uppercase (e.g., `\"INF\"`).\n * - `\"FULL\"`: The full log level name in uppercase (e.g., `\"INFO\"`).\n * - `\"L\"`: The first letter of the log level in uppercase (e.g., `\"I\"`).\n * - `\"abbr\"`: The log level abbreviation in lowercase (e.g., `\"inf\"`).\n * - `\"full\"`: The full log level name in lowercase (e.g., `\"info\"`).\n * - `\"l\"`: The first letter of the log level in lowercase (e.g., `\"i\"`).\n *\n * Alternatively, this can be a function that accepts a log level and returns\n * a string.\n *\n * The default is `\"ABBR\"`.\n */\n level?:\n | \"ABBR\"\n | \"FULL\"\n | \"L\"\n | \"abbr\"\n | \"full\"\n | \"l\"\n | ((level: LogLevel) => string);\n\n /**\n * The separator between category names. For example, if the separator is\n * `\"·\"`, the category `[\"a\", \"b\", \"c\"]` will be formatted as `\"a·b·c\"`.\n * The default separator is `\"·\"`.\n *\n * If this is a function, it will be called with the category array and\n * should return a string, which will be used for rendering the category.\n */\n category?: string | ((category: readonly string[]) => string);\n\n /**\n * The format of the embedded values.\n *\n * A function that renders a value to a string. This function is used to\n * render the values in the log record. The default is a cross-runtime\n * `inspect()` function that uses [`util.inspect()`] in Node.js/Bun,\n * [`Deno.inspect()`] in Deno, or falls back to {@link JSON.stringify} in\n * browsers.\n *\n * The second parameter provides access to the default cross-runtime\n * `inspect()` function, allowing you to fall back to the default behavior\n * for certain values while customizing others. You can ignore this\n * parameter if you don't need the fallback functionality.\n *\n * [`util.inspect()`]: https://nodejs.org/api/util.html#utilinspectobject-options\n * [`Deno.inspect()`]: https://docs.deno.com/api/deno/~/Deno.inspect\n * @param value The value to render.\n * @param inspect The default cross-runtime inspect function that can be used\n * as a fallback. Accepts an optional `options` parameter\n * with a `colors` boolean field.\n * @returns The string representation of the value.\n * @example\n * ```typescript\n * getTextFormatter({\n * value(value, inspect) {\n * // Custom formatting for numbers\n * if (typeof value === 'number') {\n * return value.toFixed(2);\n * }\n * // Fall back to default for everything else\n * return inspect(value);\n * }\n * })\n * ```\n */\n value?: (\n value: unknown,\n inspect: (value: unknown, options?: { colors?: boolean }) => string,\n ) => string;\n\n /**\n * How those formatted parts are concatenated.\n *\n * A function that formats the log record. This function is called with the\n * formatted values and should return a string. Note that the formatted\n * *should not* include a newline character at the end.\n *\n * By default, this is a function that formats the log record as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n * @param values The formatted values.\n * @returns The formatted log record.\n */\n format?: (values: FormattedValues) => string;\n\n /**\n * Line ending style for formatted output.\n *\n * - `\"lf\"`: Unix-style line endings (`\\n`)\n * - `\"crlf\"`: Windows-style line endings (`\\r\\n`)\n *\n * @default \"lf\"\n * @since 2.0.0\n */\n lineEnding?: \"lf\" | \"crlf\";\n}\n\n// Optimized helper functions for timestamp formatting\nfunction padZero(num: number): string {\n return num < 10 ? `0${num}` : `${num}`;\n}\n\nfunction padThree(num: number): string {\n return num < 10 ? `00${num}` : num < 100 ? `0${num}` : `${num}`;\n}\n\ntype TimestampPattern =\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\";\n\ntype TimeZoneConfig =\n | { kind: \"utc\" }\n | { kind: \"local\" }\n | { kind: \"offset\"; minutes: number }\n | { kind: \"iana\"; formatter: Intl.DateTimeFormat };\n\ntype DateParts = {\n year: string;\n month: string;\n day: string;\n hour: string;\n minute: string;\n second: string;\n ms: string;\n offsetMinutes: number;\n};\n\nconst fixedOffsetPattern = /^([+-])(0\\d|1\\d|2[0-3]):([0-5]\\d)$/;\n\nfunction formatOffset(minutes: number, full: boolean): string {\n const sign = minutes < 0 ? \"-\" : \"+\";\n const absolute = Math.abs(minutes);\n const hour = padZero(Math.floor(absolute / 60));\n const minute = padZero(absolute % 60);\n if (!full && minute === \"00\") return `${sign}${hour}`;\n return `${sign}${hour}:${minute}`;\n}\n\nfunction readPartsFromFormatter(\n formatter: Intl.DateTimeFormat,\n ts: number,\n): Omit<DateParts, \"ms\" | \"offsetMinutes\"> {\n const parts = formatter.formatToParts(new Date(ts));\n let year = \"\";\n let month = \"\";\n let day = \"\";\n let hour = \"\";\n let minute = \"\";\n let second = \"\";\n for (const part of parts) {\n if (part.type === \"year\") year = part.value;\n else if (part.type === \"month\") month = part.value;\n else if (part.type === \"day\") day = part.value;\n else if (part.type === \"hour\") hour = part.value;\n else if (part.type === \"minute\") minute = part.value;\n else if (part.type === \"second\") second = part.value;\n }\n return { year, month, day, hour, minute, second };\n}\n\nfunction getDateParts(ts: number, config: TimeZoneConfig): DateParts {\n const d = new Date(ts);\n const ms = padThree(d.getUTCMilliseconds());\n\n if (config.kind === \"utc\") {\n return {\n year: `${d.getUTCFullYear()}`,\n month: padZero(d.getUTCMonth() + 1),\n day: padZero(d.getUTCDate()),\n hour: padZero(d.getUTCHours()),\n minute: padZero(d.getUTCMinutes()),\n second: padZero(d.getUTCSeconds()),\n ms,\n offsetMinutes: 0,\n };\n }\n\n if (config.kind === \"local\") {\n return {\n year: `${d.getFullYear()}`,\n month: padZero(d.getMonth() + 1),\n day: padZero(d.getDate()),\n hour: padZero(d.getHours()),\n minute: padZero(d.getMinutes()),\n second: padZero(d.getSeconds()),\n ms,\n offsetMinutes: -d.getTimezoneOffset(),\n };\n }\n\n if (config.kind === \"offset\") {\n const shifted = new Date(ts + config.minutes * 60_000);\n return {\n year: `${shifted.getUTCFullYear()}`,\n month: padZero(shifted.getUTCMonth() + 1),\n day: padZero(shifted.getUTCDate()),\n hour: padZero(shifted.getUTCHours()),\n minute: padZero(shifted.getUTCMinutes()),\n second: padZero(shifted.getUTCSeconds()),\n ms,\n offsetMinutes: config.minutes,\n };\n }\n\n const parts = readPartsFromFormatter(config.formatter, ts);\n const asUtc = Date.UTC(\n Number(parts.year),\n Number(parts.month) - 1,\n Number(parts.day),\n Number(parts.hour),\n Number(parts.minute),\n Number(parts.second),\n d.getUTCMilliseconds(),\n );\n const offsetMinutes = Math.round((asUtc - ts) / 60_000);\n return { ...parts, ms, offsetMinutes };\n}\n\nfunction resolveTimeZone(timeZone: string | null | undefined): TimeZoneConfig {\n if (typeof timeZone === \"undefined\") return { kind: \"utc\" };\n if (timeZone === null) return { kind: \"local\" };\n\n const offsetMatch = fixedOffsetPattern.exec(timeZone);\n if (offsetMatch != null) {\n const sign = offsetMatch[1] === \"-\" ? -1 : 1;\n const hours = Number(offsetMatch[2]);\n const minutes = Number(offsetMatch[3]);\n return { kind: \"offset\", minutes: sign * (hours * 60 + minutes) };\n }\n\n if (\n typeof Intl === \"undefined\" || typeof Intl.DateTimeFormat !== \"function\"\n ) {\n throw new TypeError(\n `Invalid timeZone option: ${\n JSON.stringify(timeZone)\n }. This environment does not support IANA time zones.`,\n );\n }\n\n try {\n return {\n kind: \"iana\",\n formatter: new Intl.DateTimeFormat(\"en-CA\", {\n timeZone,\n hour12: false,\n hourCycle: \"h23\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n }),\n };\n } catch {\n throw new TypeError(\n `Invalid timeZone option: ${\n JSON.stringify(timeZone)\n }. Expected an IANA time zone name (e.g., \"Asia/Seoul\") or a fixed UTC offset string (e.g., \"+09:00\").`,\n );\n }\n}\n\nfunction createTimestampFormatter(\n pattern: TimestampPattern,\n timeZone: TimeZoneConfig,\n): (ts: number) => string | null {\n if (pattern === \"none\") return () => null;\n if (pattern === \"rfc3339\" && timeZone.kind === \"utc\") {\n return (ts: number): string => new Date(ts).toISOString();\n }\n\n return (ts: number): string => {\n const parts = getDateParts(ts, timeZone);\n const date = `${parts.year}-${parts.month}-${parts.day}`;\n const time = `${parts.hour}:${parts.minute}:${parts.second}.${parts.ms}`;\n const tzLong = formatOffset(parts.offsetMinutes, true);\n const tzShort = formatOffset(parts.offsetMinutes, false);\n\n if (pattern === \"date-time-timezone\") return `${date} ${time} ${tzLong}`;\n if (pattern === \"date-time-tz\") return `${date} ${time} ${tzShort}`;\n if (pattern === \"date-time\") return `${date} ${time}`;\n if (pattern === \"time-timezone\") return `${time} ${tzLong}`;\n if (pattern === \"time-tz\") return `${time} ${tzShort}`;\n if (pattern === \"time\") return time;\n if (pattern === \"date\") return date;\n return `${date}T${time}${tzLong}`;\n };\n}\n\n// Pre-computed level renderers for common cases\nconst levelRenderersCache = {\n ABBR: levelAbbreviations,\n abbr: {\n trace: \"trc\",\n debug: \"dbg\",\n info: \"inf\",\n warning: \"wrn\",\n error: \"err\",\n fatal: \"ftl\",\n } as const,\n FULL: {\n trace: \"TRACE\",\n debug: \"DEBUG\",\n info: \"INFO\",\n warning: \"WARNING\",\n error: \"ERROR\",\n fatal: \"FATAL\",\n } as const,\n full: {\n trace: \"trace\",\n debug: \"debug\",\n info: \"info\",\n warning: \"warning\",\n error: \"error\",\n fatal: \"fatal\",\n } as const,\n L: {\n trace: \"T\",\n debug: \"D\",\n info: \"I\",\n warning: \"W\",\n error: \"E\",\n fatal: \"F\",\n } as const,\n l: {\n trace: \"t\",\n debug: \"d\",\n info: \"i\",\n warning: \"w\",\n error: \"e\",\n fatal: \"f\",\n } as const,\n} as const;\n\n/**\n * Helper function to get the line ending value based on the option.\n * @param lineEnding The line ending option.\n * @returns The line ending string.\n */\nfunction getLineEndingValue(lineEnding?: \"lf\" | \"crlf\"): string {\n return lineEnding === \"crlf\" ? \"\\r\\n\" : \"\\n\";\n}\n\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (!(value instanceof Error)) return value;\n\n const serialized: Record<string, unknown> = {\n name: value.name,\n message: value.message,\n };\n\n if (typeof value.stack === \"string\") {\n serialized.stack = value.stack;\n }\n\n const cause = (value as { cause?: unknown }).cause;\n if (cause !== undefined) {\n serialized.cause = cause;\n }\n\n if (\n typeof AggregateError !== \"undefined\" &&\n value instanceof AggregateError\n ) {\n serialized.errors = value.errors;\n }\n\n for (const key of Object.keys(value)) {\n if (!(key in serialized)) {\n serialized[key] = (value as unknown as Record<string, unknown>)[key];\n }\n }\n\n return serialized;\n}\n\n/**\n * Get a text formatter with the specified options. Although it's flexible\n * enough to create a custom formatter, if you want more control, you can\n * create a custom formatter that satisfies the {@link TextFormatter} type\n * instead.\n *\n * For more information on the options, see {@link TextFormatterOptions}.\n *\n * By default, the formatter formats log records as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n * @param options The options for the text formatter.\n * @returns The text formatter.\n * @since 0.6.0\n */\nexport function getTextFormatter(\n options: TextFormatterOptions = {},\n): TextFormatter {\n // Pre-compute timestamp formatter with optimized lookup\n const timestampRenderer = (() => {\n const tsOption = options.timestamp;\n const timeZone = resolveTimeZone(options.timeZone);\n if (tsOption == null) {\n return createTimestampFormatter(\"date-time-timezone\", timeZone);\n } else if (tsOption === \"disabled\") {\n return createTimestampFormatter(\"none\", timeZone);\n } else if (\n typeof tsOption === \"string\" &&\n (\n tsOption === \"date-time-timezone\" ||\n tsOption === \"date-time-tz\" ||\n tsOption === \"date-time\" ||\n tsOption === \"time-timezone\" ||\n tsOption === \"time-tz\" ||\n tsOption === \"time\" ||\n tsOption === \"date\" ||\n tsOption === \"rfc3339\" ||\n tsOption === \"none\"\n )\n ) {\n return createTimestampFormatter(tsOption, timeZone);\n } else {\n return tsOption as (ts: number) => string | null;\n }\n })();\n\n const categorySeparator = options.category ?? \"·\";\n const valueRenderer = options.value\n ? (v: unknown) => options.value!(v, inspect)\n : inspect;\n\n // Pre-compute level renderer for better performance\n const levelRenderer = (() => {\n const levelOption = options.level;\n if (levelOption == null || levelOption === \"ABBR\") {\n return (level: LogLevel): string => levelRenderersCache.ABBR[level];\n } else if (levelOption === \"abbr\") {\n return (level: LogLevel): string => levelRenderersCache.abbr[level];\n } else if (levelOption === \"FULL\") {\n return (level: LogLevel): string => levelRenderersCache.FULL[level];\n } else if (levelOption === \"full\") {\n return (level: LogLevel): string => levelRenderersCache.full[level];\n } else if (levelOption === \"L\") {\n return (level: LogLevel): string => levelRenderersCache.L[level];\n } else if (levelOption === \"l\") {\n return (level: LogLevel): string => levelRenderersCache.l[level];\n } else {\n return levelOption;\n }\n })();\n\n const lineEnding = getLineEndingValue(options.lineEnding);\n\n const formatter: (values: FormattedValues) => string = options.format ??\n (({ timestamp, level, category, message }: FormattedValues) =>\n `${timestamp ? `${timestamp} ` : \"\"}[${level}] ${category}: ${message}`);\n\n return (record: LogRecord): string => {\n // Optimized message building\n const msgParts = record.message;\n const msgLen = msgParts.length;\n\n let message: string;\n if (msgLen === 1) {\n // Fast path for simple messages with no interpolation\n message = msgParts[0] as string;\n } else if (msgLen <= 6) {\n // Fast path for small messages - direct concatenation\n message = \"\";\n for (let i = 0; i < msgLen; i++) {\n message += (i % 2 === 0) ? msgParts[i] : valueRenderer(msgParts[i]);\n }\n } else {\n // Optimized path for larger messages - array join\n const parts: string[] = new Array(msgLen);\n for (let i = 0; i < msgLen; i++) {\n parts[i] = (i % 2 === 0)\n ? msgParts[i] as string\n : valueRenderer(msgParts[i]);\n }\n message = parts.join(\"\");\n }\n\n const timestamp = timestampRenderer(record.timestamp);\n const level = levelRenderer(record.level);\n const category = typeof categorySeparator === \"function\"\n ? categorySeparator(record.category)\n : record.category.join(categorySeparator);\n\n const values: FormattedValues = {\n timestamp,\n level,\n category,\n message,\n record,\n };\n return `${formatter(values)}${lineEnding}`;\n };\n}\n\n/**\n * The default text formatter. This formatter formats log records as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n *\n * @param record The log record to format.\n * @returns The formatted log record.\n */\nexport const defaultTextFormatter: TextFormatter = getTextFormatter();\n\nconst RESET = \"\\x1b[0m\";\n\n/**\n * The ANSI colors. These can be used to colorize text in the console.\n * @since 0.6.0\n */\nexport type AnsiColor =\n | \"black\"\n | \"red\"\n | \"green\"\n | \"yellow\"\n | \"blue\"\n | \"magenta\"\n | \"cyan\"\n | \"white\";\n\nconst ansiColors: Record<AnsiColor, string> = {\n black: \"\\x1b[30m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n};\n\n/**\n * The ANSI text styles.\n * @since 0.6.0\n */\nexport type AnsiStyle =\n | \"bold\"\n | \"dim\"\n | \"italic\"\n | \"underline\"\n | \"strikethrough\";\n\nconst ansiStyles: Record<AnsiStyle, string> = {\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n italic: \"\\x1b[3m\",\n underline: \"\\x1b[4m\",\n strikethrough: \"\\x1b[9m\",\n};\n\nconst defaultLevelColors: Record<LogLevel, AnsiColor | null> = {\n trace: null,\n debug: \"blue\",\n info: \"green\",\n warning: \"yellow\",\n error: \"red\",\n fatal: \"magenta\",\n};\n\n/**\n * The various options for the ANSI color formatter.\n * @since 0.6.0\n */\nexport interface AnsiColorFormatterOptions extends TextFormatterOptions {\n /**\n * The timestamp format. This can be one of the following:\n *\n * - `\"date-time-timezone\"`: The date and time with the full timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00:00\"`).\n * - `\"date-time-tz\"`: The date and time with the short timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00\"`).\n * - `\"date-time\"`: The date and time without the timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000\"`).\n * - `\"time-timezone\"`: The time with the full timezone offset but without\n * the date (e.g., `\"22:13:20.000 +00:00\"`).\n * - `\"time-tz\"`: The time with the short timezone offset but without the date\n * (e.g., `\"22:13:20.000 +00\"`).\n * - `\"time\"`: The time without the date or timezone offset\n * (e.g., `\"22:13:20.000\"`).\n * - `\"date\"`: The date without the time or timezone offset\n * (e.g., `\"2023-11-14\"`).\n * - `\"rfc3339\"`: The date and time in RFC 3339 format\n * (e.g., `\"2023-11-14T22:13:20.000Z\"`).\n *\n * Alternatively, this can be a function that accepts a timestamp and returns\n * a string.\n *\n * The default is `\"date-time-tz\"`.\n */\n timestamp?:\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\"\n | \"disabled\"\n | ((ts: number) => string | null);\n\n /**\n * The ANSI style for the timestamp. `\"dim\"` is used by default.\n */\n timestampStyle?: AnsiStyle | null;\n\n /**\n * The ANSI color for the timestamp. No color is used by default.\n */\n timestampColor?: AnsiColor | null;\n\n /**\n * The ANSI style for the log level. `\"bold\"` is used by default.\n */\n levelStyle?: AnsiStyle | null;\n\n /**\n * The ANSI colors for the log levels. The default colors are as follows:\n *\n * - `\"trace\"`: `null` (no color)\n * - `\"debug\"`: `\"blue\"`\n * - `\"info\"`: `\"green\"`\n * - `\"warning\"`: `\"yellow\"`\n * - `\"error\"`: `\"red\"`\n * - `\"fatal\"`: `\"magenta\"`\n */\n levelColors?: Record<LogLevel, AnsiColor | null>;\n\n /**\n * The ANSI style for the category. `\"dim\"` is used by default.\n */\n categoryStyle?: AnsiStyle | null;\n\n /**\n * The ANSI color for the category. No color is used by default.\n */\n categoryColor?: AnsiColor | null;\n}\n\n/**\n * Get an ANSI color formatter with the specified options.\n *\n * \n * @param option The options for the ANSI color formatter.\n * @returns The ANSI color formatter.\n * @since 0.6.0\n */\nexport function getAnsiColorFormatter(\n options: AnsiColorFormatterOptions = {},\n): TextFormatter {\n const format = options.format;\n const timestampStyle = typeof options.timestampStyle === \"undefined\"\n ? \"dim\"\n : options.timestampStyle;\n const timestampColor = options.timestampColor ?? null;\n const timestampPrefix = `${\n timestampStyle == null ? \"\" : ansiStyles[timestampStyle]\n }${timestampColor == null ? \"\" : ansiColors[timestampColor]}`;\n const timestampSuffix = timestampStyle == null && timestampColor == null\n ? \"\"\n : RESET;\n const levelStyle = typeof options.levelStyle === \"undefined\"\n ? \"bold\"\n : options.levelStyle;\n const levelColors = options.levelColors ?? defaultLevelColors;\n const categoryStyle = typeof options.categoryStyle === \"undefined\"\n ? \"dim\"\n : options.categoryStyle;\n const categoryColor = options.categoryColor ?? null;\n const categoryPrefix = `${\n categoryStyle == null ? \"\" : ansiStyles[categoryStyle]\n }${categoryColor == null ? \"\" : ansiColors[categoryColor]}`;\n const categorySuffix = categoryStyle == null && categoryColor == null\n ? \"\"\n : RESET;\n return getTextFormatter({\n timestamp: \"date-time-tz\",\n value(value: unknown, fallbackInspect): string {\n return fallbackInspect(value, { colors: true });\n },\n ...options,\n format({ timestamp, level, category, message, record }): string {\n const levelColor = levelColors[record.level];\n timestamp = timestamp == null\n ? null\n : `${timestampPrefix}${timestamp}${timestampSuffix}`;\n level = `${levelStyle == null ? \"\" : ansiStyles[levelStyle]}${\n levelColor == null ? \"\" : ansiColors[levelColor]\n }${level}${levelStyle == null && levelColor == null ? \"\" : RESET}`;\n return format == null\n ? `${\n timestamp == null ? \"\" : `${timestamp} `\n }${level} ${categoryPrefix}${category}:${categorySuffix} ${message}`\n : format({\n timestamp,\n level,\n category: `${categoryPrefix}${category}${categorySuffix}`,\n message,\n record,\n });\n },\n });\n}\n\n/**\n * A text formatter that uses ANSI colors to format log records.\n *\n * \n *\n * @param record The log record to format.\n * @returns The formatted log record.\n * @since 0.5.0\n */\nexport const ansiColorFormatter: TextFormatter = getAnsiColorFormatter();\n\n/**\n * Options for the {@link getJsonLinesFormatter} function.\n * @since 0.11.0\n */\nexport interface JsonLinesFormatterOptions {\n /**\n * The separator between category names. For example, if the separator is\n * `\".\"`, the category `[\"a\", \"b\", \"c\"]` will be formatted as `\"a.b.c\"`.\n * If this is a function, it will be called with the category array and\n * should return a string or an array of strings, which will be used\n * for rendering the category.\n *\n * @default `\".\"`\n */\n readonly categorySeparator?:\n | string\n | ((category: readonly string[]) => string | readonly string[]);\n\n /**\n * The message format. This can be one of the following:\n *\n * - `\"template\"`: The raw message template is used as the message.\n * - `\"rendered\"`: The message is rendered with the values.\n *\n * @default `\"rendered\"`\n */\n readonly message?: \"template\" | \"rendered\";\n\n /**\n * The properties format. This can be one of the following:\n *\n * - `\"flatten\"`: The properties are flattened into the root object.\n * - `\"prepend:<prefix>\"`: The properties are prepended with the given prefix\n * (e.g., `\"prepend:ctx_\"` will prepend `ctx_` to each property key).\n * - `\"nest:<key>\"`: The properties are nested under the given key\n * (e.g., `\"nest:properties\"` will nest the properties under the\n * `properties` key).\n *\n * @default `\"nest:properties\"`\n */\n readonly properties?: \"flatten\" | `prepend:${string}` | `nest:${string}`;\n\n /**\n * Line ending style for formatted output.\n *\n * - `\"lf\"`: Unix-style line endings (`\\n`)\n * - `\"crlf\"`: Windows-style line endings (`\\r\\n`)\n *\n * @default \"lf\"\n * @since 2.0.0\n */\n readonly lineEnding?: \"lf\" | \"crlf\";\n}\n\n/**\n * Get a [JSON Lines] formatter with the specified options. The log records\n * will be rendered as JSON objects, one per line, which is a common format\n * for log files. This format is also known as Newline-Delimited JSON (NDJSON).\n * It looks like this:\n *\n * ```json\n * {\"@timestamp\":\"2023-11-14T22:13:20.000Z\",\"level\":\"INFO\",\"message\":\"Hello, world!\",\"logger\":\"my.logger\",\"properties\":{\"key\":\"value\"}}\n * ```\n *\n * [JSON Lines]: https://jsonlines.org/\n * @param options The options for the JSON Lines formatter.\n * @returns The JSON Lines formatter.\n * @since 0.11.0\n */\nexport function getJsonLinesFormatter(\n options: JsonLinesFormatterOptions = {},\n): TextFormatter {\n const lineEnding = getLineEndingValue(options.lineEnding);\n\n // Most common configuration - optimize for the default case\n if (!options.categorySeparator && !options.message && !options.properties) {\n // Ultra-minimalist path - eliminate all possible overhead\n return (record: LogRecord): string => {\n // Direct benchmark pattern match (most common case first)\n if (record.message.length === 3) {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\"\n ? \"WARN\"\n : record.level.toUpperCase(),\n message: record.message[0] + JSON.stringify(record.message[1]) +\n record.message[2],\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n }\n\n // Single message (second most common)\n if (record.message.length === 1) {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\"\n ? \"WARN\"\n : record.level.toUpperCase(),\n message: record.message[0],\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n }\n\n // Complex messages (fallback)\n let msg = record.message[0] as string;\n for (let i = 1; i < record.message.length; i++) {\n msg += (i & 1) ? JSON.stringify(record.message[i]) : record.message[i];\n }\n\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\" ? \"WARN\" : record.level.toUpperCase(),\n message: msg,\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n };\n }\n\n // Pre-compile configuration for non-default cases\n const isTemplateMessage = options.message === \"template\";\n const propertiesOption = options.properties ?? \"nest:properties\";\n\n // Pre-compile category joining strategy\n let joinCategory: (category: readonly string[]) => string | readonly string[];\n if (typeof options.categorySeparator === \"function\") {\n joinCategory = options.categorySeparator;\n } else {\n const separator = options.categorySeparator ?? \".\";\n joinCategory = (category: readonly string[]): string =>\n category.join(separator);\n }\n\n // Pre-compile properties handling strategy\n let getProperties: (\n properties: Record<string, unknown>,\n ) => Record<string, unknown>;\n\n if (propertiesOption === \"flatten\") {\n getProperties = (properties) => properties;\n } else if (propertiesOption.startsWith(\"prepend:\")) {\n const prefix = propertiesOption.substring(8);\n if (prefix === \"\") {\n throw new TypeError(\n `Invalid properties option: ${\n JSON.stringify(propertiesOption)\n }. It must be of the form \"prepend:<prefix>\" where <prefix> is a non-empty string.`,\n );\n }\n getProperties = (properties) => {\n const result: Record<string, unknown> = {};\n for (const key in properties) {\n result[`${prefix}${key}`] = properties[key];\n }\n return result;\n };\n } else if (propertiesOption.startsWith(\"nest:\")) {\n const key = propertiesOption.substring(5);\n getProperties = (properties) => ({ [key]: properties });\n } else {\n throw new TypeError(\n `Invalid properties option: ${\n JSON.stringify(propertiesOption)\n }. It must be \"flatten\", \"prepend:<prefix>\", or \"nest:<key>\".`,\n );\n }\n\n // Pre-compile message rendering function\n let getMessage: (record: LogRecord) => string;\n\n if (isTemplateMessage) {\n getMessage = (record: LogRecord): string => {\n if (typeof record.rawMessage === \"string\") {\n return record.rawMessage;\n }\n let msg = \"\";\n for (let i = 0; i < record.rawMessage.length; i++) {\n msg += i % 2 < 1 ? record.rawMessage[i] : \"{}\";\n }\n return msg;\n };\n } else {\n getMessage = (record: LogRecord): string => {\n const msgLen = record.message.length;\n\n if (msgLen === 1) {\n return record.message[0] as string;\n }\n\n let msg = \"\";\n for (let i = 0; i < msgLen; i++) {\n msg += (i % 2 < 1)\n ? record.message[i]\n : JSON.stringify(record.message[i]);\n }\n return msg;\n };\n }\n\n return (record: LogRecord): string => {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\" ? \"WARN\" : record.level.toUpperCase(),\n message: getMessage(record),\n logger: joinCategory(record.category),\n ...getProperties(record.properties),\n }, jsonReplacer) + lineEnding;\n };\n}\n\n/**\n * The default [JSON Lines] formatter. This formatter formats log records\n * as JSON objects, one per line, which is a common format for log files.\n * It looks like this:\n *\n * ```json\n * {\"@timestamp\":\"2023-11-14T22:13:20.000Z\",\"level\":\"INFO\",\"message\":\"Hello, world!\",\"logger\":\"my.logger\",\"properties\":{\"key\":\"value\"}}\n * ```\n *\n * You can customize the output by passing options to\n * {@link getJsonLinesFormatter}. For example, you can change the category\n * separator, the message format, and how the properties are formatted.\n *\n * [JSON Lines]: https://jsonlines.org/\n * @since 0.11.0\n */\nexport const jsonLinesFormatter: TextFormatter = getJsonLinesFormatter();\n\n/**\n * A console formatter is a function that accepts a log record and returns\n * an array of arguments to pass to {@link console.log}.\n *\n * @param record The log record to format.\n * @returns The formatted log record, as an array of arguments for\n * {@link console.log}.\n */\nexport type ConsoleFormatter = (record: LogRecord) => readonly unknown[];\n\n/**\n * The styles for the log level in the console.\n */\nconst logLevelStyles: Record<LogLevel, string> = {\n \"trace\": \"background-color: gray; color: white;\",\n \"debug\": \"background-color: gray; color: white;\",\n \"info\": \"background-color: white; color: black;\",\n \"warning\": \"background-color: orange; color: black;\",\n \"error\": \"background-color: red; color: white;\",\n \"fatal\": \"background-color: maroon; color: white;\",\n};\n\n/**\n * The default console formatter.\n *\n * @param record The log record to format.\n * @returns The formatted log record, as an array of arguments for\n * {@link console.log}.\n */\nexport function defaultConsoleFormatter(record: LogRecord): readonly unknown[] {\n let msg = \"\";\n const values: unknown[] = [];\n for (let i = 0; i < record.message.length; i++) {\n if (i % 2 === 0) msg += record.message[i];\n else {\n msg += \"%o\";\n values.push(record.message[i]);\n }\n }\n const date = new Date(record.timestamp);\n const time = `${date.getUTCHours().toString().padStart(2, \"0\")}:${\n date.getUTCMinutes().toString().padStart(2, \"0\")\n }:${date.getUTCSeconds().toString().padStart(2, \"0\")}.${\n date.getUTCMilliseconds().toString().padStart(3, \"0\")\n }`;\n return [\n `%c${time} %c${levelAbbreviations[record.level]}%c %c${\n record.category.join(\"\\xb7\")\n } %c${msg}`,\n \"color: gray;\",\n logLevelStyles[record.level],\n \"background-color: default;\",\n \"color: gray;\",\n \"color: default;\",\n ...values,\n ];\n}\n"],"mappings":";;;;;;AAgBA,MAAMA,qBAA+C;CACnD,SAAS;CACT,SAAS;CACT,QAAQ;CACR,WAAW;CACX,SAAS;CACT,SAAS;AACV;;;;;;;;;;;AAYD,MAAMC,iBAGG,aAAa,sBAGX,cAAc,eAAe,UAAU,YAAY,gBACxD,CAAC,MAAM,KAAK,UAAU,EAAE,GAGxB,UAAU,cAAc,aAAa,WAAW,eAGvC,WAAW,KAAK,YAAY,aACrC,CAAC,GAAG,SAGJ,WAAW,KAAK,QAAQ,GAAG;CACzB,mBAAmB;CACnB,eAAe;CACf,GAAG;AACJ,EAAC,GAGF,QAAQ,QAAQ,aAAa,eAAe,KAAK,YAAY,aAC7D,CAAC,GAAG,SAGJ,KAAK,QAAQ,GAAG;CACd,gBAAgB;CAChB,iBAAiB;CACjB,GAAG;AACJ,EAAC,GACF,CAAC,MAAM,KAAK,UAAU,EAAE;AAiM9B,SAAS,QAAQC,KAAqB;AACpC,QAAO,MAAM,MAAM,GAAG,IAAI,KAAK,EAAE,IAAI;AACtC;AAED,SAAS,SAASA,KAAqB;AACrC,QAAO,MAAM,MAAM,IAAI,IAAI,IAAI,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE,IAAI;AAC/D;AA8BD,MAAM,qBAAqB;AAE3B,SAAS,aAAaC,SAAiBC,MAAuB;CAC5D,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,MAAM,WAAW,KAAK,IAAI,QAAQ;CAClC,MAAM,OAAO,QAAQ,KAAK,MAAM,WAAW,GAAG,CAAC;CAC/C,MAAM,SAAS,QAAQ,WAAW,GAAG;AACrC,MAAK,QAAQ,WAAW,KAAM,SAAQ,EAAE,KAAK,EAAE,KAAK;AACpD,SAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;AACjC;AAED,SAAS,uBACPC,WACAC,IACyC;CACzC,MAAM,QAAQ,UAAU,cAAc,IAAI,KAAK,IAAI;CACnD,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,IAAI,MAAM;CACV,IAAI,OAAO;CACX,IAAI,SAAS;CACb,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;UAC7B,KAAK,SAAS,QAAS,SAAQ,KAAK;UACpC,KAAK,SAAS,MAAO,OAAM,KAAK;UAChC,KAAK,SAAS,OAAQ,QAAO,KAAK;UAClC,KAAK,SAAS,SAAU,UAAS,KAAK;UACtC,KAAK,SAAS,SAAU,UAAS,KAAK;AAEjD,QAAO;EAAE;EAAM;EAAO;EAAK;EAAM;EAAQ;CAAQ;AAClD;AAED,SAAS,aAAaA,IAAYC,QAAmC;CACnE,MAAM,IAAI,IAAI,KAAK;CACnB,MAAM,KAAK,SAAS,EAAE,oBAAoB,CAAC;AAE3C,KAAI,OAAO,SAAS,MAClB,QAAO;EACL,OAAO,EAAE,EAAE,gBAAgB,CAAC;EAC5B,OAAO,QAAQ,EAAE,aAAa,GAAG,EAAE;EACnC,KAAK,QAAQ,EAAE,YAAY,CAAC;EAC5B,MAAM,QAAQ,EAAE,aAAa,CAAC;EAC9B,QAAQ,QAAQ,EAAE,eAAe,CAAC;EAClC,QAAQ,QAAQ,EAAE,eAAe,CAAC;EAClC;EACA,eAAe;CAChB;AAGH,KAAI,OAAO,SAAS,QAClB,QAAO;EACL,OAAO,EAAE,EAAE,aAAa,CAAC;EACzB,OAAO,QAAQ,EAAE,UAAU,GAAG,EAAE;EAChC,KAAK,QAAQ,EAAE,SAAS,CAAC;EACzB,MAAM,QAAQ,EAAE,UAAU,CAAC;EAC3B,QAAQ,QAAQ,EAAE,YAAY,CAAC;EAC/B,QAAQ,QAAQ,EAAE,YAAY,CAAC;EAC/B;EACA,gBAAgB,EAAE,mBAAmB;CACtC;AAGH,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,UAAU,IAAI,KAAK,KAAK,OAAO,UAAU;AAC/C,SAAO;GACL,OAAO,EAAE,QAAQ,gBAAgB,CAAC;GAClC,OAAO,QAAQ,QAAQ,aAAa,GAAG,EAAE;GACzC,KAAK,QAAQ,QAAQ,YAAY,CAAC;GAClC,MAAM,QAAQ,QAAQ,aAAa,CAAC;GACpC,QAAQ,QAAQ,QAAQ,eAAe,CAAC;GACxC,QAAQ,QAAQ,QAAQ,eAAe,CAAC;GACxC;GACA,eAAe,OAAO;EACvB;CACF;CAED,MAAM,QAAQ,uBAAuB,OAAO,WAAW,GAAG;CAC1D,MAAM,QAAQ,KAAK,IACjB,OAAO,MAAM,KAAK,EAClB,OAAO,MAAM,MAAM,GAAG,GACtB,OAAO,MAAM,IAAI,EACjB,OAAO,MAAM,KAAK,EAClB,OAAO,MAAM,OAAO,EACpB,OAAO,MAAM,OAAO,EACpB,EAAE,oBAAoB,CACvB;CACD,MAAM,gBAAgB,KAAK,OAAO,QAAQ,MAAM,IAAO;AACvD,QAAO;EAAE,GAAG;EAAO;EAAI;CAAe;AACvC;AAED,SAAS,gBAAgBC,UAAqD;AAC5E,YAAW,aAAa,YAAa,QAAO,EAAE,MAAM,MAAO;AAC3D,KAAI,aAAa,KAAM,QAAO,EAAE,MAAM,QAAS;CAE/C,MAAM,cAAc,mBAAmB,KAAK,SAAS;AACrD,KAAI,eAAe,MAAM;EACvB,MAAM,OAAO,YAAY,OAAO,MAAM,KAAK;EAC3C,MAAM,QAAQ,OAAO,YAAY,GAAG;EACpC,MAAM,UAAU,OAAO,YAAY,GAAG;AACtC,SAAO;GAAE,MAAM;GAAU,SAAS,QAAQ,QAAQ,KAAK;EAAU;CAClE;AAED,YACS,SAAS,sBAAsB,KAAK,mBAAmB,WAE9D,OAAM,IAAI,WACP,2BACC,KAAK,UAAU,SAAS,CACzB;AAIL,KAAI;AACF,SAAO;GACL,MAAM;GACN,WAAW,IAAI,KAAK,eAAe,SAAS;IAC1C;IACA,QAAQ;IACR,WAAW;IACX,MAAM;IACN,OAAO;IACP,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;GACT;EACF;CACF,QAAO;AACN,QAAM,IAAI,WACP,2BACC,KAAK,UAAU,SAAS,CACzB;CAEJ;AACF;AAED,SAAS,yBACPC,SACAC,UAC+B;AAC/B,KAAI,YAAY,OAAQ,QAAO,MAAM;AACrC,KAAI,YAAY,aAAa,SAAS,SAAS,MAC7C,QAAO,CAACJ,OAAuB,IAAI,KAAK,IAAI,aAAa;AAG3D,QAAO,CAACA,OAAuB;EAC7B,MAAM,QAAQ,aAAa,IAAI,SAAS;EACxC,MAAM,QAAQ,EAAE,MAAM,KAAK,GAAG,MAAM,MAAM,GAAG,MAAM,IAAI;EACvD,MAAM,QAAQ,EAAE,MAAM,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,GAAG,MAAM,GAAG;EACvE,MAAM,SAAS,aAAa,MAAM,eAAe,KAAK;EACtD,MAAM,UAAU,aAAa,MAAM,eAAe,MAAM;AAExD,MAAI,YAAY,qBAAsB,SAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO;AACvE,MAAI,YAAY,eAAgB,SAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ;AAClE,MAAI,YAAY,YAAa,SAAQ,EAAE,KAAK,GAAG,KAAK;AACpD,MAAI,YAAY,gBAAiB,SAAQ,EAAE,KAAK,GAAG,OAAO;AAC1D,MAAI,YAAY,UAAW,SAAQ,EAAE,KAAK,GAAG,QAAQ;AACrD,MAAI,YAAY,OAAQ,QAAO;AAC/B,MAAI,YAAY,OAAQ,QAAO;AAC/B,UAAQ,EAAE,KAAK,GAAG,KAAK,EAAE,OAAO;CACjC;AACF;AAGD,MAAM,sBAAsB;CAC1B,MAAM;CACN,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,GAAG;EACD,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,GAAG;EACD,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;AACF;;;;;;AAOD,SAAS,mBAAmBK,YAAoC;AAC9D,QAAO,eAAe,SAAS,SAAS;AACzC;AAED,SAAS,aAAaC,MAAcC,OAAyB;AAC3D,OAAM,iBAAiB,OAAQ,QAAO;CAEtC,MAAMC,aAAsC;EAC1C,MAAM,MAAM;EACZ,SAAS,MAAM;CAChB;AAED,YAAW,MAAM,UAAU,SACzB,YAAW,QAAQ,MAAM;CAG3B,MAAM,QAAS,MAA8B;AAC7C,KAAI,iBACF,YAAW,QAAQ;AAGrB,YACS,mBAAmB,eAC1B,iBAAiB,eAEjB,YAAW,SAAS,MAAM;AAG5B,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAClC,OAAM,OAAO,YACX,YAAW,OAAQ,MAA6C;AAIpE,QAAO;AACR;;;;;;;;;;;;;;;;;;AAmBD,SAAgB,iBACdC,UAAgC,CAAE,GACnB;CAEf,MAAM,oBAAoB,CAAC,MAAM;EAC/B,MAAM,WAAW,QAAQ;EACzB,MAAM,WAAW,gBAAgB,QAAQ,SAAS;AAClD,MAAI,YAAY,KACd,QAAO,yBAAyB,sBAAsB,SAAS;WACtD,aAAa,WACtB,QAAO,yBAAyB,QAAQ,SAAS;kBAE1C,aAAa,aAElB,aAAa,wBACb,aAAa,kBACb,aAAa,eACb,aAAa,mBACb,aAAa,aACb,aAAa,UACb,aAAa,UACb,aAAa,aACb,aAAa,QAGf,QAAO,yBAAyB,UAAU,SAAS;MAEnD,QAAO;CAEV,IAAG;CAEJ,MAAM,oBAAoB,QAAQ,YAAY;CAC9C,MAAM,gBAAgB,QAAQ,QAC1B,CAACC,MAAe,QAAQ,MAAO,GAAG,QAAQ,GAC1C;CAGJ,MAAM,gBAAgB,CAAC,MAAM;EAC3B,MAAM,cAAc,QAAQ;AAC5B,MAAI,eAAe,QAAQ,gBAAgB,OACzC,QAAO,CAACC,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,IACzB,QAAO,CAACA,UAA4B,oBAAoB,EAAE;WACjD,gBAAgB,IACzB,QAAO,CAACA,UAA4B,oBAAoB,EAAE;MAE1D,QAAO;CAEV,IAAG;CAEJ,MAAM,aAAa,mBAAmB,QAAQ,WAAW;CAEzD,MAAMC,YAAiD,QAAQ,WAC5D,CAAC,EAAE,WAAW,OAAO,UAAU,SAA0B,MACvD,EAAE,aAAa,EAAE,UAAU,KAAK,GAAG,GAAG,MAAM,IAAI,SAAS,IAAI,QAAQ;AAE1E,QAAO,CAACC,WAA8B;EAEpC,MAAM,WAAW,OAAO;EACxB,MAAM,SAAS,SAAS;EAExB,IAAIC;AACJ,MAAI,WAAW,EAEb,WAAU,SAAS;WACV,UAAU,GAAG;AAEtB,aAAU;AACV,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,YAAY,IAAI,MAAM,IAAK,SAAS,KAAK,cAAc,SAAS,GAAG;EAEtE,OAAM;GAEL,MAAMC,QAAkB,IAAI,MAAM;AAClC,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,OAAM,KAAM,IAAI,MAAM,IAClB,SAAS,KACT,cAAc,SAAS,GAAG;AAEhC,aAAU,MAAM,KAAK,GAAG;EACzB;EAED,MAAM,YAAY,kBAAkB,OAAO,UAAU;EACrD,MAAM,QAAQ,cAAc,OAAO,MAAM;EACzC,MAAM,kBAAkB,sBAAsB,aAC1C,kBAAkB,OAAO,SAAS,GAClC,OAAO,SAAS,KAAK,kBAAkB;EAE3C,MAAMC,SAA0B;GAC9B;GACA;GACA;GACA;GACA;EACD;AACD,UAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,WAAW;CAC1C;AACF;;;;;;;;;;;AAYD,MAAaC,uBAAsC,kBAAkB;AAErE,MAAM,QAAQ;AAgBd,MAAMC,aAAwC;CAC5C,OAAO;CACP,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,SAAS;CACT,MAAM;CACN,OAAO;AACR;AAaD,MAAMC,aAAwC;CAC5C,MAAM;CACN,KAAK;CACL,QAAQ;CACR,WAAW;CACX,eAAe;AAChB;AAED,MAAMC,qBAAyD;CAC7D,OAAO;CACP,OAAO;CACP,MAAM;CACN,SAAS;CACT,OAAO;CACP,OAAO;AACR;;;;;;;;;AA2FD,SAAgB,sBACdC,UAAqC,CAAE,GACxB;CACf,MAAM,SAAS,QAAQ;CACvB,MAAM,wBAAwB,QAAQ,mBAAmB,cACrD,QACA,QAAQ;CACZ,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,mBAAmB,EACvB,kBAAkB,OAAO,KAAK,WAAW,gBAC1C,EAAE,kBAAkB,OAAO,KAAK,WAAW,gBAAgB;CAC5D,MAAM,kBAAkB,kBAAkB,QAAQ,kBAAkB,OAChE,KACA;CACJ,MAAM,oBAAoB,QAAQ,eAAe,cAC7C,SACA,QAAQ;CACZ,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,uBAAuB,QAAQ,kBAAkB,cACnD,QACA,QAAQ;CACZ,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,kBAAkB,EACtB,iBAAiB,OAAO,KAAK,WAAW,eACzC,EAAE,iBAAiB,OAAO,KAAK,WAAW,eAAe;CAC1D,MAAM,iBAAiB,iBAAiB,QAAQ,iBAAiB,OAC7D,KACA;AACJ,QAAO,iBAAiB;EACtB,WAAW;EACX,MAAMd,OAAgB,iBAAyB;AAC7C,UAAO,gBAAgB,OAAO,EAAE,QAAQ,KAAM,EAAC;EAChD;EACD,GAAG;EACH,OAAO,EAAE,WAAW,OAAO,UAAU,SAAS,QAAQ,EAAU;GAC9D,MAAM,aAAa,YAAY,OAAO;AACtC,eAAY,aAAa,OACrB,QACC,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB;AACrD,YAAS,EAAE,cAAc,OAAO,KAAK,WAAW,YAAY,EAC1D,cAAc,OAAO,KAAK,WAAW,YACtC,EAAE,MAAM,EAAE,cAAc,QAAQ,cAAc,OAAO,KAAK,MAAM;AACjE,UAAO,UAAU,QACZ,EACD,aAAa,OAAO,MAAM,EAAE,UAAU,GACvC,EAAE,MAAM,GAAG,eAAe,EAAE,SAAS,GAAG,eAAe,GAAG,QAAQ,IACjE,OAAO;IACP;IACA;IACA,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe;IACxD;IACA;GACD,EAAC;EACL;CACF,EAAC;AACH;;;;;;;;;;AAWD,MAAae,qBAAoC,uBAAuB;;;;;;;;;;;;;;;;AAuExE,SAAgB,sBACdC,UAAqC,CAAE,GACxB;CACf,MAAM,aAAa,mBAAmB,QAAQ,WAAW;AAGzD,MAAK,QAAQ,sBAAsB,QAAQ,YAAY,QAAQ,WAE7D,QAAO,CAACV,WAA8B;AAEpC,MAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YACpB,SACA,OAAO,MAAM,aAAa;GAC9B,SAAS,OAAO,QAAQ,KAAK,KAAK,UAAU,OAAO,QAAQ,GAAG,GAC5D,OAAO,QAAQ;GACjB,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;AAIrB,MAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YACpB,SACA,OAAO,MAAM,aAAa;GAC9B,SAAS,OAAO,QAAQ;GACxB,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;EAIrB,IAAI,MAAM,OAAO,QAAQ;AACzB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IACzC,QAAQ,IAAI,IAAK,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG,OAAO,QAAQ;AAGtE,SAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YAAY,SAAS,OAAO,MAAM,aAAa;GACvE,SAAS;GACT,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;CACpB;CAIH,MAAM,oBAAoB,QAAQ,YAAY;CAC9C,MAAM,mBAAmB,QAAQ,cAAc;CAG/C,IAAIW;AACJ,YAAW,QAAQ,sBAAsB,WACvC,gBAAe,QAAQ;MAClB;EACL,MAAM,YAAY,QAAQ,qBAAqB;AAC/C,iBAAe,CAACC,aACd,SAAS,KAAK,UAAU;CAC3B;CAGD,IAAIC;AAIJ,KAAI,qBAAqB,UACvB,iBAAgB,CAAC,eAAe;UACvB,iBAAiB,WAAW,WAAW,EAAE;EAClD,MAAM,SAAS,iBAAiB,UAAU,EAAE;AAC5C,MAAI,WAAW,GACb,OAAM,IAAI,WACP,6BACC,KAAK,UAAU,iBAAiB,CACjC;AAGL,kBAAgB,CAAC,eAAe;GAC9B,MAAMC,SAAkC,CAAE;AAC1C,QAAK,MAAM,OAAO,WAChB,SAAQ,EAAE,OAAO,EAAE,IAAI,KAAK,WAAW;AAEzC,UAAO;EACR;CACF,WAAU,iBAAiB,WAAW,QAAQ,EAAE;EAC/C,MAAM,MAAM,iBAAiB,UAAU,EAAE;AACzC,kBAAgB,CAAC,gBAAgB,GAAG,MAAM,WAAY;CACvD,MACC,OAAM,IAAI,WACP,6BACC,KAAK,UAAU,iBAAiB,CACjC;CAKL,IAAIC;AAEJ,KAAI,kBACF,cAAa,CAACf,WAA8B;AAC1C,aAAW,OAAO,eAAe,SAC/B,QAAO,OAAO;EAEhB,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,WAAW,QAAQ,IAC5C,QAAO,IAAI,IAAI,IAAI,OAAO,WAAW,KAAK;AAE5C,SAAO;CACR;KAED,cAAa,CAACA,WAA8B;EAC1C,MAAM,SAAS,OAAO,QAAQ;AAE9B,MAAI,WAAW,EACb,QAAO,OAAO,QAAQ;EAGxB,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,QAAQ,IAAI,IAAI,IACZ,OAAO,QAAQ,KACf,KAAK,UAAU,OAAO,QAAQ,GAAG;AAEvC,SAAO;CACR;AAGH,QAAO,CAACA,WAA8B;AACpC,SAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YAAY,SAAS,OAAO,MAAM,aAAa;GACvE,SAAS,WAAW,OAAO;GAC3B,QAAQ,aAAa,OAAO,SAAS;GACrC,GAAG,cAAc,OAAO,WAAW;EACpC,GAAE,aAAa,GAAG;CACpB;AACF;;;;;;;;;;;;;;;;;AAkBD,MAAagB,qBAAoC,uBAAuB;;;;AAexE,MAAMC,iBAA2C;CAC/C,SAAS;CACT,SAAS;CACT,QAAQ;CACR,WAAW;CACX,SAAS;CACT,SAAS;AACV;;;;;;;;AASD,SAAgB,wBAAwBjB,QAAuC;CAC7E,IAAI,MAAM;CACV,MAAMkB,SAAoB,CAAE;AAC5B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IACzC,KAAI,IAAI,MAAM,EAAG,QAAO,OAAO,QAAQ;MAClC;AACH,SAAO;AACP,SAAO,KAAK,OAAO,QAAQ,GAAG;CAC/B;CAEH,MAAM,OAAO,IAAI,KAAK,OAAO;CAC7B,MAAM,QAAQ,EAAE,KAAK,aAAa,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GAC7D,KAAK,eAAe,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CACjD,GAAG,KAAK,eAAe,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GACnD,KAAK,oBAAoB,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CACtD;AACD,QAAO;GACJ,IAAI,KAAK,KAAK,mBAAmB,OAAO,OAAO,OAC9C,OAAO,SAAS,KAAK,IAAO,CAC7B,KAAK,IAAI;EACV;EACA,eAAe,OAAO;EACtB;EACA;EACA;EACA,GAAG;CACJ;AACF"}
|
|
1
|
+
{"version":3,"file":"formatter.js","names":["levelAbbreviations: Record<LogLevel, string>","inspect: (value: unknown, options?: { colors?: boolean }) => string","num: number","minutes: number","full: boolean","formatter: Intl.DateTimeFormat","ts: number","config: TimeZoneConfig","timeZone: string | null | undefined","pattern: TimestampPattern","timeZone: TimeZoneConfig","lineEnding?: \"lf\" | \"crlf\"","_key: string","value: unknown","serialized: Record<string, unknown>","options: TextFormatterOptions","v: unknown","level: LogLevel","formatter: (values: FormattedValues) => string","record: LogRecord","message: string","parts: string[]","values: FormattedValues","defaultTextFormatter: TextFormatter","ansiColors: Record<AnsiColor, string>","ansiStyles: Record<AnsiStyle, string>","defaultLevelColors: Record<LogLevel, AnsiColor | null>","options: AnsiColorFormatterOptions","ansiColorFormatter: TextFormatter","options: JsonLinesFormatterOptions","joinCategory: (category: readonly string[]) => string | readonly string[]","category: readonly string[]","getProperties: (\n properties: Record<string, unknown>,\n ) => Record<string, unknown>","result: Record<string, unknown>","getMessage: (record: LogRecord) => string","jsonLinesFormatter: TextFormatter","logLevelStyles: Record<LogLevel, string>","values: unknown[]"],"sources":["../src/formatter.ts"],"sourcesContent":["import * as util from \"#util\";\nimport type { LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A text formatter is a function that accepts a log record and returns\n * a string.\n *\n * @param record The log record to format.\n * @returns The formatted log record.\n */\nexport type TextFormatter = (record: LogRecord) => string;\n\n/**\n * The severity level abbreviations.\n */\nconst levelAbbreviations: Record<LogLevel, string> = {\n \"trace\": \"TRC\",\n \"debug\": \"DBG\",\n \"info\": \"INF\",\n \"warning\": \"WRN\",\n \"error\": \"ERR\",\n \"fatal\": \"FTL\",\n};\n\n/**\n * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},\n * and in Node.js/Bun it is `util.inspect()`. If neither is available, it\n * falls back to {@link JSON.stringify}.\n *\n * @param value The value to inspect.\n * @param options The options for inspecting the value.\n * If `colors` is `true`, the output will be ANSI-colored.\n * @returns The string representation of the value.\n */\nconst inspect: (value: unknown, options?: { colors?: boolean }) => string =\n // @ts-ignore: Browser detection\n // dnt-shim-ignore\n typeof document !== \"undefined\" ||\n // @ts-ignore: React Native detection\n // dnt-shim-ignore\n typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\"\n ? (v) => JSON.stringify(v)\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n : \"Deno\" in globalThis && \"inspect\" in globalThis.Deno &&\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n typeof globalThis.Deno.inspect === \"function\"\n ? (v, opts) =>\n // @ts-ignore: Deno global\n // dnt-shim-ignore\n globalThis.Deno.inspect(v, {\n strAbbreviateSize: Infinity,\n iterableLimit: Infinity,\n ...opts,\n })\n // @ts-ignore: Node.js global\n // dnt-shim-ignore\n : util != null && \"inspect\" in util && typeof util.inspect === \"function\"\n ? (v, opts) =>\n // @ts-ignore: Node.js global\n // dnt-shim-ignore\n util.inspect(v, {\n maxArrayLength: Infinity,\n maxStringLength: Infinity,\n ...opts,\n })\n : (v) => JSON.stringify(v);\n\n/**\n * The formatted values for a log record.\n * @since 0.6.0\n */\nexport interface FormattedValues {\n /**\n * The formatted timestamp.\n */\n timestamp: string | null;\n\n /**\n * The formatted log level.\n */\n level: string;\n\n /**\n * The formatted category.\n */\n category: string;\n\n /**\n * The formatted message.\n */\n message: string;\n\n /**\n * The unformatted log record.\n */\n record: LogRecord;\n}\n\n/**\n * The various options for the built-in text formatters.\n * @since 0.6.0\n */\nexport interface TextFormatterOptions {\n /**\n * The timestamp format. This can be one of the following:\n *\n * - `\"date-time-timezone\"`: The date and time with the full timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00:00\"`).\n * - `\"date-time-tz\"`: The date and time with the short timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00\"`).\n * - `\"date-time\"`: The date and time without the timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000\"`).\n * - `\"time-timezone\"`: The time with the full timezone offset but without\n * the date (e.g., `\"22:13:20.000 +00:00\"`).\n * - `\"time-tz\"`: The time with the short timezone offset but without the date\n * (e.g., `\"22:13:20.000 +00\"`).\n * - `\"time\"`: The time without the date or timezone offset\n * (e.g., `\"22:13:20.000\"`).\n * - `\"date\"`: The date without the time or timezone offset\n * (e.g., `\"2023-11-14\"`).\n * - `\"rfc3339\"`: The date and time in RFC 3339 format\n * (e.g., `\"2023-11-14T22:13:20.000Z\"`).\n * - `\"none\"` or `\"disabled\"`: No display\n *\n * Alternatively, this can be a function that accepts a timestamp and returns\n * a string.\n *\n * The default is `\"date-time-timezone\"`.\n */\n timestamp?:\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\"\n | \"disabled\"\n | ((ts: number) => string | null);\n\n /**\n * The timezone used for timestamp rendering.\n *\n * - `undefined` (default): UTC (preserves existing behavior)\n * - `null`: System local timezone\n * - IANA timezone name such as `\"America/Bogota\"` or `\"Asia/Seoul\"`\n * - Fixed UTC offset string such as `\"+09:00\"` or `\"-05:00\"`\n *\n * @since 2.1.0\n */\n timeZone?: string | null;\n\n /**\n * The log level format. This can be one of the following:\n *\n * - `\"ABBR\"`: The log level abbreviation in uppercase (e.g., `\"INF\"`).\n * - `\"FULL\"`: The full log level name in uppercase (e.g., `\"INFO\"`).\n * - `\"L\"`: The first letter of the log level in uppercase (e.g., `\"I\"`).\n * - `\"abbr\"`: The log level abbreviation in lowercase (e.g., `\"inf\"`).\n * - `\"full\"`: The full log level name in lowercase (e.g., `\"info\"`).\n * - `\"l\"`: The first letter of the log level in lowercase (e.g., `\"i\"`).\n *\n * Alternatively, this can be a function that accepts a log level and returns\n * a string.\n *\n * The default is `\"ABBR\"`.\n */\n level?:\n | \"ABBR\"\n | \"FULL\"\n | \"L\"\n | \"abbr\"\n | \"full\"\n | \"l\"\n | ((level: LogLevel) => string);\n\n /**\n * The separator between category names. For example, if the separator is\n * `\"·\"`, the category `[\"a\", \"b\", \"c\"]` will be formatted as `\"a·b·c\"`.\n * The default separator is `\"·\"`.\n *\n * If this is a function, it will be called with the category array and\n * should return a string, which will be used for rendering the category.\n */\n category?: string | ((category: readonly string[]) => string);\n\n /**\n * The format of the embedded values.\n *\n * A function that renders a value to a string. This function is used to\n * render the values in the log record. The default is a cross-runtime\n * `inspect()` function that uses [`util.inspect()`] in Node.js/Bun,\n * [`Deno.inspect()`] in Deno, or falls back to {@link JSON.stringify} in\n * browsers.\n *\n * The second parameter provides access to the default cross-runtime\n * `inspect()` function, allowing you to fall back to the default behavior\n * for certain values while customizing others. You can ignore this\n * parameter if you don't need the fallback functionality.\n *\n * [`util.inspect()`]: https://nodejs.org/api/util.html#utilinspectobject-options\n * [`Deno.inspect()`]: https://docs.deno.com/api/deno/~/Deno.inspect\n * @param value The value to render.\n * @param inspect The default cross-runtime inspect function that can be used\n * as a fallback. Accepts an optional `options` parameter\n * with a `colors` boolean field.\n * @returns The string representation of the value.\n * @example\n * ```typescript\n * getTextFormatter({\n * value(value, inspect) {\n * // Custom formatting for numbers\n * if (typeof value === 'number') {\n * return value.toFixed(2);\n * }\n * // Fall back to default for everything else\n * return inspect(value);\n * }\n * })\n * ```\n */\n value?: (\n value: unknown,\n inspect: (value: unknown, options?: { colors?: boolean }) => string,\n ) => string;\n\n /**\n * How those formatted parts are concatenated.\n *\n * A function that formats the log record. This function is called with the\n * formatted values and should return a string. Note that the formatted\n * *should not* include a newline character at the end.\n *\n * By default, this is a function that formats the log record as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n * @param values The formatted values.\n * @returns The formatted log record.\n */\n format?: (values: FormattedValues) => string;\n\n /**\n * Line ending style for formatted output.\n *\n * - `\"lf\"`: Unix-style line endings (`\\n`)\n * - `\"crlf\"`: Windows-style line endings (`\\r\\n`)\n *\n * @default \"lf\"\n * @since 2.0.0\n */\n lineEnding?: \"lf\" | \"crlf\";\n}\n\n// Optimized helper functions for timestamp formatting\nfunction padZero(num: number): string {\n return num < 10 ? `0${num}` : `${num}`;\n}\n\nfunction padThree(num: number): string {\n return num < 10 ? `00${num}` : num < 100 ? `0${num}` : `${num}`;\n}\n\ntype TimestampPattern =\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\";\n\ntype TimeZoneConfig =\n | { kind: \"utc\" }\n | { kind: \"local\" }\n | { kind: \"offset\"; minutes: number }\n | { kind: \"iana\"; formatter: Intl.DateTimeFormat };\n\ntype DateParts = {\n year: string;\n month: string;\n day: string;\n hour: string;\n minute: string;\n second: string;\n ms: string;\n offsetMinutes: number;\n};\n\nconst fixedOffsetPattern = /^([+-])(0\\d|1\\d|2[0-3]):([0-5]\\d)$/;\n\nfunction formatOffset(minutes: number, full: boolean): string {\n const sign = minutes < 0 ? \"-\" : \"+\";\n const absolute = Math.abs(minutes);\n const hour = padZero(Math.floor(absolute / 60));\n const minute = padZero(absolute % 60);\n if (!full && minute === \"00\") return `${sign}${hour}`;\n return `${sign}${hour}:${minute}`;\n}\n\nfunction readPartsFromFormatter(\n formatter: Intl.DateTimeFormat,\n ts: number,\n): Omit<DateParts, \"ms\" | \"offsetMinutes\"> {\n const parts = formatter.formatToParts(new Date(ts));\n let year = \"\";\n let month = \"\";\n let day = \"\";\n let hour = \"\";\n let minute = \"\";\n let second = \"\";\n for (const part of parts) {\n if (part.type === \"year\") year = part.value;\n else if (part.type === \"month\") month = part.value;\n else if (part.type === \"day\") day = part.value;\n else if (part.type === \"hour\") hour = part.value;\n else if (part.type === \"minute\") minute = part.value;\n else if (part.type === \"second\") second = part.value;\n }\n return { year, month, day, hour, minute, second };\n}\n\nfunction getDateParts(ts: number, config: TimeZoneConfig): DateParts {\n const d = new Date(ts);\n const ms = padThree(d.getUTCMilliseconds());\n\n if (config.kind === \"utc\") {\n return {\n year: `${d.getUTCFullYear()}`,\n month: padZero(d.getUTCMonth() + 1),\n day: padZero(d.getUTCDate()),\n hour: padZero(d.getUTCHours()),\n minute: padZero(d.getUTCMinutes()),\n second: padZero(d.getUTCSeconds()),\n ms,\n offsetMinutes: 0,\n };\n }\n\n if (config.kind === \"local\") {\n return {\n year: `${d.getFullYear()}`,\n month: padZero(d.getMonth() + 1),\n day: padZero(d.getDate()),\n hour: padZero(d.getHours()),\n minute: padZero(d.getMinutes()),\n second: padZero(d.getSeconds()),\n ms,\n offsetMinutes: -d.getTimezoneOffset(),\n };\n }\n\n if (config.kind === \"offset\") {\n const shifted = new Date(ts + config.minutes * 60_000);\n return {\n year: `${shifted.getUTCFullYear()}`,\n month: padZero(shifted.getUTCMonth() + 1),\n day: padZero(shifted.getUTCDate()),\n hour: padZero(shifted.getUTCHours()),\n minute: padZero(shifted.getUTCMinutes()),\n second: padZero(shifted.getUTCSeconds()),\n ms,\n offsetMinutes: config.minutes,\n };\n }\n\n const parts = readPartsFromFormatter(config.formatter, ts);\n const asUtc = Date.UTC(\n Number(parts.year),\n Number(parts.month) - 1,\n Number(parts.day),\n Number(parts.hour),\n Number(parts.minute),\n Number(parts.second),\n d.getUTCMilliseconds(),\n );\n const offsetMinutes = Math.round((asUtc - ts) / 60_000);\n return { ...parts, ms, offsetMinutes };\n}\n\nfunction resolveTimeZone(timeZone: string | null | undefined): TimeZoneConfig {\n if (typeof timeZone === \"undefined\") return { kind: \"utc\" };\n if (timeZone === null) return { kind: \"local\" };\n\n const offsetMatch = fixedOffsetPattern.exec(timeZone);\n if (offsetMatch != null) {\n const sign = offsetMatch[1] === \"-\" ? -1 : 1;\n const hours = Number(offsetMatch[2]);\n const minutes = Number(offsetMatch[3]);\n return { kind: \"offset\", minutes: sign * (hours * 60 + minutes) };\n }\n\n if (\n typeof Intl === \"undefined\" || typeof Intl.DateTimeFormat !== \"function\"\n ) {\n throw new TypeError(\n `Invalid timeZone option: ${\n JSON.stringify(timeZone)\n }. This environment does not support IANA time zones.`,\n );\n }\n\n try {\n return {\n kind: \"iana\",\n formatter: new Intl.DateTimeFormat(\"en-CA\", {\n timeZone,\n hour12: false,\n hourCycle: \"h23\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n }),\n };\n } catch {\n throw new TypeError(\n `Invalid timeZone option: ${\n JSON.stringify(timeZone)\n }. Expected an IANA time zone name (e.g., \"Asia/Seoul\") or a fixed UTC offset string (e.g., \"+09:00\").`,\n );\n }\n}\n\nfunction createTimestampFormatter(\n pattern: TimestampPattern,\n timeZone: TimeZoneConfig,\n): (ts: number) => string | null {\n if (pattern === \"none\") return () => null;\n if (pattern === \"rfc3339\" && timeZone.kind === \"utc\") {\n return (ts: number): string => new Date(ts).toISOString();\n }\n\n return (ts: number): string => {\n const parts = getDateParts(ts, timeZone);\n const date = `${parts.year}-${parts.month}-${parts.day}`;\n const time = `${parts.hour}:${parts.minute}:${parts.second}.${parts.ms}`;\n const tzLong = formatOffset(parts.offsetMinutes, true);\n const tzShort = formatOffset(parts.offsetMinutes, false);\n\n if (pattern === \"date-time-timezone\") return `${date} ${time} ${tzLong}`;\n if (pattern === \"date-time-tz\") return `${date} ${time} ${tzShort}`;\n if (pattern === \"date-time\") return `${date} ${time}`;\n if (pattern === \"time-timezone\") return `${time} ${tzLong}`;\n if (pattern === \"time-tz\") return `${time} ${tzShort}`;\n if (pattern === \"time\") return time;\n if (pattern === \"date\") return date;\n return `${date}T${time}${tzLong}`;\n };\n}\n\n// Pre-computed level renderers for common cases\nconst levelRenderersCache = {\n ABBR: levelAbbreviations,\n abbr: {\n trace: \"trc\",\n debug: \"dbg\",\n info: \"inf\",\n warning: \"wrn\",\n error: \"err\",\n fatal: \"ftl\",\n } as const,\n FULL: {\n trace: \"TRACE\",\n debug: \"DEBUG\",\n info: \"INFO\",\n warning: \"WARNING\",\n error: \"ERROR\",\n fatal: \"FATAL\",\n } as const,\n full: {\n trace: \"trace\",\n debug: \"debug\",\n info: \"info\",\n warning: \"warning\",\n error: \"error\",\n fatal: \"fatal\",\n } as const,\n L: {\n trace: \"T\",\n debug: \"D\",\n info: \"I\",\n warning: \"W\",\n error: \"E\",\n fatal: \"F\",\n } as const,\n l: {\n trace: \"t\",\n debug: \"d\",\n info: \"i\",\n warning: \"w\",\n error: \"e\",\n fatal: \"f\",\n } as const,\n} as const;\n\n/**\n * Helper function to get the line ending value based on the option.\n * @param lineEnding The line ending option.\n * @returns The line ending string.\n */\nfunction getLineEndingValue(lineEnding?: \"lf\" | \"crlf\"): string {\n return lineEnding === \"crlf\" ? \"\\r\\n\" : \"\\n\";\n}\n\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (!(value instanceof Error)) return value;\n\n const serialized: Record<string, unknown> = {\n name: value.name,\n message: value.message,\n };\n\n if (typeof value.stack === \"string\") {\n serialized.stack = value.stack;\n }\n\n const cause = (value as { cause?: unknown }).cause;\n if (cause !== undefined) {\n serialized.cause = cause;\n }\n\n if (\n typeof AggregateError !== \"undefined\" &&\n value instanceof AggregateError\n ) {\n serialized.errors = value.errors;\n }\n\n for (const key of Object.keys(value)) {\n if (!(key in serialized)) {\n serialized[key] = (value as unknown as Record<string, unknown>)[key];\n }\n }\n\n return serialized;\n}\n\n/**\n * Get a text formatter with the specified options. Although it's flexible\n * enough to create a custom formatter, if you want more control, you can\n * create a custom formatter that satisfies the {@link TextFormatter} type\n * instead.\n *\n * For more information on the options, see {@link TextFormatterOptions}.\n *\n * By default, the formatter formats log records as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n * @param options The options for the text formatter.\n * @returns The text formatter.\n * @since 0.6.0\n */\nexport function getTextFormatter(\n options: TextFormatterOptions = {},\n): TextFormatter {\n // Pre-compute timestamp formatter with optimized lookup\n const timestampRenderer = (() => {\n const tsOption = options.timestamp;\n const timeZone = resolveTimeZone(options.timeZone);\n if (tsOption == null) {\n return createTimestampFormatter(\"date-time-timezone\", timeZone);\n } else if (tsOption === \"disabled\") {\n return createTimestampFormatter(\"none\", timeZone);\n } else if (\n typeof tsOption === \"string\" &&\n (\n tsOption === \"date-time-timezone\" ||\n tsOption === \"date-time-tz\" ||\n tsOption === \"date-time\" ||\n tsOption === \"time-timezone\" ||\n tsOption === \"time-tz\" ||\n tsOption === \"time\" ||\n tsOption === \"date\" ||\n tsOption === \"rfc3339\" ||\n tsOption === \"none\"\n )\n ) {\n return createTimestampFormatter(tsOption, timeZone);\n } else {\n return tsOption as (ts: number) => string | null;\n }\n })();\n\n const categorySeparator = options.category ?? \"·\";\n const valueRenderer = options.value\n ? (v: unknown) => options.value!(v, inspect)\n : inspect;\n\n // Pre-compute level renderer for better performance\n const levelRenderer = (() => {\n const levelOption = options.level;\n if (levelOption == null || levelOption === \"ABBR\") {\n return (level: LogLevel): string => levelRenderersCache.ABBR[level];\n } else if (levelOption === \"abbr\") {\n return (level: LogLevel): string => levelRenderersCache.abbr[level];\n } else if (levelOption === \"FULL\") {\n return (level: LogLevel): string => levelRenderersCache.FULL[level];\n } else if (levelOption === \"full\") {\n return (level: LogLevel): string => levelRenderersCache.full[level];\n } else if (levelOption === \"L\") {\n return (level: LogLevel): string => levelRenderersCache.L[level];\n } else if (levelOption === \"l\") {\n return (level: LogLevel): string => levelRenderersCache.l[level];\n } else {\n return levelOption;\n }\n })();\n\n const lineEnding = getLineEndingValue(options.lineEnding);\n\n const formatter: (values: FormattedValues) => string = options.format ??\n (({ timestamp, level, category, message }: FormattedValues) =>\n `${timestamp ? `${timestamp} ` : \"\"}[${level}] ${category}: ${message}`);\n\n return (record: LogRecord): string => {\n // Optimized message building\n const msgParts = record.message;\n const msgLen = msgParts.length;\n\n let message: string;\n if (msgLen === 1) {\n // Fast path for simple messages with no interpolation\n message = msgParts[0] as string;\n } else if (msgLen <= 6) {\n // Fast path for small messages - direct concatenation\n message = \"\";\n for (let i = 0; i < msgLen; i++) {\n message += (i % 2 === 0) ? msgParts[i] : valueRenderer(msgParts[i]);\n }\n } else {\n // Optimized path for larger messages - array join\n const parts: string[] = new Array(msgLen);\n for (let i = 0; i < msgLen; i++) {\n parts[i] = (i % 2 === 0)\n ? msgParts[i] as string\n : valueRenderer(msgParts[i]);\n }\n message = parts.join(\"\");\n }\n\n const timestamp = timestampRenderer(record.timestamp);\n const level = levelRenderer(record.level);\n const category = typeof categorySeparator === \"function\"\n ? categorySeparator(record.category)\n : record.category.join(categorySeparator);\n\n const values: FormattedValues = {\n timestamp,\n level,\n category,\n message,\n record,\n };\n return `${formatter(values)}${lineEnding}`;\n };\n}\n\n/**\n * The default text formatter. This formatter formats log records as follows:\n *\n * ```\n * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!\n * ```\n *\n * @param record The log record to format.\n * @returns The formatted log record.\n */\nexport const defaultTextFormatter: TextFormatter = getTextFormatter();\n\nconst RESET = \"\\x1b[0m\";\n\n/**\n * The ANSI colors. These can be used to colorize text in the console.\n * @since 0.6.0\n */\nexport type AnsiColor =\n | \"black\"\n | \"red\"\n | \"green\"\n | \"yellow\"\n | \"blue\"\n | \"magenta\"\n | \"cyan\"\n | \"white\";\n\nconst ansiColors: Record<AnsiColor, string> = {\n black: \"\\x1b[30m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n};\n\n/**\n * The ANSI text styles.\n * @since 0.6.0\n */\nexport type AnsiStyle =\n | \"bold\"\n | \"dim\"\n | \"italic\"\n | \"underline\"\n | \"strikethrough\";\n\nconst ansiStyles: Record<AnsiStyle, string> = {\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n italic: \"\\x1b[3m\",\n underline: \"\\x1b[4m\",\n strikethrough: \"\\x1b[9m\",\n};\n\nconst defaultLevelColors: Record<LogLevel, AnsiColor | null> = {\n trace: null,\n debug: \"blue\",\n info: \"green\",\n warning: \"yellow\",\n error: \"red\",\n fatal: \"magenta\",\n};\n\n/**\n * The various options for the ANSI color formatter.\n * @since 0.6.0\n */\nexport interface AnsiColorFormatterOptions extends TextFormatterOptions {\n /**\n * The timestamp format. This can be one of the following:\n *\n * - `\"date-time-timezone\"`: The date and time with the full timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00:00\"`).\n * - `\"date-time-tz\"`: The date and time with the short timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000 +00\"`).\n * - `\"date-time\"`: The date and time without the timezone offset\n * (e.g., `\"2023-11-14 22:13:20.000\"`).\n * - `\"time-timezone\"`: The time with the full timezone offset but without\n * the date (e.g., `\"22:13:20.000 +00:00\"`).\n * - `\"time-tz\"`: The time with the short timezone offset but without the date\n * (e.g., `\"22:13:20.000 +00\"`).\n * - `\"time\"`: The time without the date or timezone offset\n * (e.g., `\"22:13:20.000\"`).\n * - `\"date\"`: The date without the time or timezone offset\n * (e.g., `\"2023-11-14\"`).\n * - `\"rfc3339\"`: The date and time in RFC 3339 format\n * (e.g., `\"2023-11-14T22:13:20.000Z\"`).\n *\n * Alternatively, this can be a function that accepts a timestamp and returns\n * a string.\n *\n * The default is `\"date-time-tz\"`.\n */\n timestamp?:\n | \"date-time-timezone\"\n | \"date-time-tz\"\n | \"date-time\"\n | \"time-timezone\"\n | \"time-tz\"\n | \"time\"\n | \"date\"\n | \"rfc3339\"\n | \"none\"\n | \"disabled\"\n | ((ts: number) => string | null);\n\n /**\n * The ANSI style for the timestamp. `\"dim\"` is used by default.\n */\n timestampStyle?: AnsiStyle | null;\n\n /**\n * The ANSI color for the timestamp. No color is used by default.\n */\n timestampColor?: AnsiColor | null;\n\n /**\n * The ANSI style for the log level. `\"bold\"` is used by default.\n */\n levelStyle?: AnsiStyle | null;\n\n /**\n * The ANSI colors for the log levels. The default colors are as follows:\n *\n * - `\"trace\"`: `null` (no color)\n * - `\"debug\"`: `\"blue\"`\n * - `\"info\"`: `\"green\"`\n * - `\"warning\"`: `\"yellow\"`\n * - `\"error\"`: `\"red\"`\n * - `\"fatal\"`: `\"magenta\"`\n */\n levelColors?: Record<LogLevel, AnsiColor | null>;\n\n /**\n * The ANSI style for the category. `\"dim\"` is used by default.\n */\n categoryStyle?: AnsiStyle | null;\n\n /**\n * The ANSI color for the category. No color is used by default.\n */\n categoryColor?: AnsiColor | null;\n}\n\n/**\n * Get an ANSI color formatter with the specified options.\n *\n * \n * @param option The options for the ANSI color formatter.\n * @returns The ANSI color formatter.\n * @since 0.6.0\n */\nexport function getAnsiColorFormatter(\n options: AnsiColorFormatterOptions = {},\n): TextFormatter {\n const format = options.format;\n const timestampStyle = typeof options.timestampStyle === \"undefined\"\n ? \"dim\"\n : options.timestampStyle;\n const timestampColor = options.timestampColor ?? null;\n const timestampPrefix = `${\n timestampStyle == null ? \"\" : ansiStyles[timestampStyle]\n }${timestampColor == null ? \"\" : ansiColors[timestampColor]}`;\n const timestampSuffix = timestampStyle == null && timestampColor == null\n ? \"\"\n : RESET;\n const levelStyle = typeof options.levelStyle === \"undefined\"\n ? \"bold\"\n : options.levelStyle;\n const levelColors = options.levelColors ?? defaultLevelColors;\n const categoryStyle = typeof options.categoryStyle === \"undefined\"\n ? \"dim\"\n : options.categoryStyle;\n const categoryColor = options.categoryColor ?? null;\n const categoryPrefix = `${\n categoryStyle == null ? \"\" : ansiStyles[categoryStyle]\n }${categoryColor == null ? \"\" : ansiColors[categoryColor]}`;\n const categorySuffix = categoryStyle == null && categoryColor == null\n ? \"\"\n : RESET;\n return getTextFormatter({\n timestamp: \"date-time-tz\",\n value(value: unknown, fallbackInspect): string {\n return fallbackInspect(value, { colors: true });\n },\n ...options,\n format({ timestamp, level, category, message, record }): string {\n const levelColor = levelColors[record.level];\n timestamp = timestamp == null\n ? null\n : `${timestampPrefix}${timestamp}${timestampSuffix}`;\n level = `${levelStyle == null ? \"\" : ansiStyles[levelStyle]}${\n levelColor == null ? \"\" : ansiColors[levelColor]\n }${level}${levelStyle == null && levelColor == null ? \"\" : RESET}`;\n return format == null\n ? `${\n timestamp == null ? \"\" : `${timestamp} `\n }${level} ${categoryPrefix}${category}:${categorySuffix} ${message}`\n : format({\n timestamp,\n level,\n category: `${categoryPrefix}${category}${categorySuffix}`,\n message,\n record,\n });\n },\n });\n}\n\n/**\n * A text formatter that uses ANSI colors to format log records.\n *\n * \n *\n * @param record The log record to format.\n * @returns The formatted log record.\n * @since 0.5.0\n */\nexport const ansiColorFormatter: TextFormatter = getAnsiColorFormatter();\n\n/**\n * Options for the {@link getJsonLinesFormatter} function.\n * @since 0.11.0\n */\nexport interface JsonLinesFormatterOptions {\n /**\n * The separator between category names. For example, if the separator is\n * `\".\"`, the category `[\"a\", \"b\", \"c\"]` will be formatted as `\"a.b.c\"`.\n * If this is a function, it will be called with the category array and\n * should return a string or an array of strings, which will be used\n * for rendering the category.\n *\n * @default `\".\"`\n */\n readonly categorySeparator?:\n | string\n | ((category: readonly string[]) => string | readonly string[]);\n\n /**\n * The message format. This can be one of the following:\n *\n * - `\"template\"`: The raw message template is used as the message.\n * - `\"rendered\"`: The message is rendered with the values.\n *\n * @default `\"rendered\"`\n */\n readonly message?: \"template\" | \"rendered\";\n\n /**\n * The properties format. This can be one of the following:\n *\n * - `\"flatten\"`: The properties are flattened into the root object.\n * - `\"prepend:<prefix>\"`: The properties are prepended with the given prefix\n * (e.g., `\"prepend:ctx_\"` will prepend `ctx_` to each property key).\n * - `\"nest:<key>\"`: The properties are nested under the given key\n * (e.g., `\"nest:properties\"` will nest the properties under the\n * `properties` key).\n *\n * @default `\"nest:properties\"`\n */\n readonly properties?: \"flatten\" | `prepend:${string}` | `nest:${string}`;\n\n /**\n * Line ending style for formatted output.\n *\n * - `\"lf\"`: Unix-style line endings (`\\n`)\n * - `\"crlf\"`: Windows-style line endings (`\\r\\n`)\n *\n * @default \"lf\"\n * @since 2.0.0\n */\n readonly lineEnding?: \"lf\" | \"crlf\";\n}\n\n/**\n * Get a [JSON Lines] formatter with the specified options. The log records\n * will be rendered as JSON objects, one per line, which is a common format\n * for log files. This format is also known as Newline-Delimited JSON (NDJSON).\n * It looks like this:\n *\n * ```json\n * {\"@timestamp\":\"2023-11-14T22:13:20.000Z\",\"level\":\"INFO\",\"message\":\"Hello, world!\",\"logger\":\"my.logger\",\"properties\":{\"key\":\"value\"}}\n * ```\n *\n * [JSON Lines]: https://jsonlines.org/\n * @param options The options for the JSON Lines formatter.\n * @returns The JSON Lines formatter.\n * @since 0.11.0\n */\nexport function getJsonLinesFormatter(\n options: JsonLinesFormatterOptions = {},\n): TextFormatter {\n const lineEnding = getLineEndingValue(options.lineEnding);\n\n // Most common configuration - optimize for the default case\n if (!options.categorySeparator && !options.message && !options.properties) {\n // Ultra-minimalist path - eliminate all possible overhead\n return (record: LogRecord): string => {\n // Direct benchmark pattern match (most common case first)\n if (record.message.length === 3) {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\"\n ? \"WARN\"\n : record.level.toUpperCase(),\n message: record.message[0] + JSON.stringify(record.message[1]) +\n record.message[2],\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n }\n\n // Single message (second most common)\n if (record.message.length === 1) {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\"\n ? \"WARN\"\n : record.level.toUpperCase(),\n message: record.message[0],\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n }\n\n // Complex messages (fallback)\n let msg = record.message[0] as string;\n for (let i = 1; i < record.message.length; i++) {\n msg += (i & 1) ? JSON.stringify(record.message[i]) : record.message[i];\n }\n\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\" ? \"WARN\" : record.level.toUpperCase(),\n message: msg,\n logger: record.category.join(\".\"),\n properties: record.properties,\n }, jsonReplacer) + lineEnding;\n };\n }\n\n // Pre-compile configuration for non-default cases\n const isTemplateMessage = options.message === \"template\";\n const propertiesOption = options.properties ?? \"nest:properties\";\n\n // Pre-compile category joining strategy\n let joinCategory: (category: readonly string[]) => string | readonly string[];\n if (typeof options.categorySeparator === \"function\") {\n joinCategory = options.categorySeparator;\n } else {\n const separator = options.categorySeparator ?? \".\";\n joinCategory = (category: readonly string[]): string =>\n category.join(separator);\n }\n\n // Pre-compile properties handling strategy\n let getProperties: (\n properties: Record<string, unknown>,\n ) => Record<string, unknown>;\n\n if (propertiesOption === \"flatten\") {\n getProperties = (properties) => properties;\n } else if (propertiesOption.startsWith(\"prepend:\")) {\n const prefix = propertiesOption.substring(8);\n if (prefix === \"\") {\n throw new TypeError(\n `Invalid properties option: ${\n JSON.stringify(propertiesOption)\n }. It must be of the form \"prepend:<prefix>\" where <prefix> is a non-empty string.`,\n );\n }\n getProperties = (properties) => {\n const result: Record<string, unknown> = {};\n for (const key in properties) {\n result[`${prefix}${key}`] = properties[key];\n }\n return result;\n };\n } else if (propertiesOption.startsWith(\"nest:\")) {\n const key = propertiesOption.substring(5);\n getProperties = (properties) => ({ [key]: properties });\n } else {\n throw new TypeError(\n `Invalid properties option: ${\n JSON.stringify(propertiesOption)\n }. It must be \"flatten\", \"prepend:<prefix>\", or \"nest:<key>\".`,\n );\n }\n\n // Pre-compile message rendering function\n let getMessage: (record: LogRecord) => string;\n\n if (isTemplateMessage) {\n getMessage = (record: LogRecord): string => {\n if (typeof record.rawMessage === \"string\") {\n return record.rawMessage;\n }\n let msg = \"\";\n for (let i = 0; i < record.rawMessage.length; i++) {\n if (i > 0) msg += \"{}\";\n msg += record.rawMessage[i];\n }\n return msg;\n };\n } else {\n getMessage = (record: LogRecord): string => {\n const msgLen = record.message.length;\n\n if (msgLen === 1) {\n return record.message[0] as string;\n }\n\n let msg = \"\";\n for (let i = 0; i < msgLen; i++) {\n msg += (i % 2 < 1)\n ? record.message[i]\n : JSON.stringify(record.message[i]);\n }\n return msg;\n };\n }\n\n return (record: LogRecord): string => {\n return JSON.stringify({\n \"@timestamp\": new Date(record.timestamp).toISOString(),\n level: record.level === \"warning\" ? \"WARN\" : record.level.toUpperCase(),\n message: getMessage(record),\n logger: joinCategory(record.category),\n ...getProperties(record.properties),\n }, jsonReplacer) + lineEnding;\n };\n}\n\n/**\n * The default [JSON Lines] formatter. This formatter formats log records\n * as JSON objects, one per line, which is a common format for log files.\n * It looks like this:\n *\n * ```json\n * {\"@timestamp\":\"2023-11-14T22:13:20.000Z\",\"level\":\"INFO\",\"message\":\"Hello, world!\",\"logger\":\"my.logger\",\"properties\":{\"key\":\"value\"}}\n * ```\n *\n * You can customize the output by passing options to\n * {@link getJsonLinesFormatter}. For example, you can change the category\n * separator, the message format, and how the properties are formatted.\n *\n * [JSON Lines]: https://jsonlines.org/\n * @since 0.11.0\n */\nexport const jsonLinesFormatter: TextFormatter = getJsonLinesFormatter();\n\n/**\n * A console formatter is a function that accepts a log record and returns\n * an array of arguments to pass to {@link console.log}.\n *\n * @param record The log record to format.\n * @returns The formatted log record, as an array of arguments for\n * {@link console.log}.\n */\nexport type ConsoleFormatter = (record: LogRecord) => readonly unknown[];\n\n/**\n * The styles for the log level in the console.\n */\nconst logLevelStyles: Record<LogLevel, string> = {\n \"trace\": \"background-color: gray; color: white;\",\n \"debug\": \"background-color: gray; color: white;\",\n \"info\": \"background-color: white; color: black;\",\n \"warning\": \"background-color: orange; color: black;\",\n \"error\": \"background-color: red; color: white;\",\n \"fatal\": \"background-color: maroon; color: white;\",\n};\n\n/**\n * The default console formatter.\n *\n * @param record The log record to format.\n * @returns The formatted log record, as an array of arguments for\n * {@link console.log}.\n */\nexport function defaultConsoleFormatter(record: LogRecord): readonly unknown[] {\n let msg = \"\";\n const values: unknown[] = [];\n for (let i = 0; i < record.message.length; i++) {\n if (i % 2 === 0) msg += record.message[i];\n else {\n msg += \"%o\";\n values.push(record.message[i]);\n }\n }\n const date = new Date(record.timestamp);\n const time = `${date.getUTCHours().toString().padStart(2, \"0\")}:${\n date.getUTCMinutes().toString().padStart(2, \"0\")\n }:${date.getUTCSeconds().toString().padStart(2, \"0\")}.${\n date.getUTCMilliseconds().toString().padStart(3, \"0\")\n }`;\n return [\n `%c${time} %c${levelAbbreviations[record.level]}%c %c${\n record.category.join(\"\\xb7\")\n } %c${msg}`,\n \"color: gray;\",\n logLevelStyles[record.level],\n \"background-color: default;\",\n \"color: gray;\",\n \"color: default;\",\n ...values,\n ];\n}\n"],"mappings":";;;;;;AAgBA,MAAMA,qBAA+C;CACnD,SAAS;CACT,SAAS;CACT,QAAQ;CACR,WAAW;CACX,SAAS;CACT,SAAS;AACV;;;;;;;;;;;AAYD,MAAMC,iBAGG,aAAa,sBAGX,cAAc,eAAe,UAAU,YAAY,gBACxD,CAAC,MAAM,KAAK,UAAU,EAAE,GAGxB,UAAU,cAAc,aAAa,WAAW,eAGvC,WAAW,KAAK,YAAY,aACrC,CAAC,GAAG,SAGJ,WAAW,KAAK,QAAQ,GAAG;CACzB,mBAAmB;CACnB,eAAe;CACf,GAAG;AACJ,EAAC,GAGF,QAAQ,QAAQ,aAAa,eAAe,KAAK,YAAY,aAC7D,CAAC,GAAG,SAGJ,KAAK,QAAQ,GAAG;CACd,gBAAgB;CAChB,iBAAiB;CACjB,GAAG;AACJ,EAAC,GACF,CAAC,MAAM,KAAK,UAAU,EAAE;AAiM9B,SAAS,QAAQC,KAAqB;AACpC,QAAO,MAAM,MAAM,GAAG,IAAI,KAAK,EAAE,IAAI;AACtC;AAED,SAAS,SAASA,KAAqB;AACrC,QAAO,MAAM,MAAM,IAAI,IAAI,IAAI,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE,IAAI;AAC/D;AA8BD,MAAM,qBAAqB;AAE3B,SAAS,aAAaC,SAAiBC,MAAuB;CAC5D,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,MAAM,WAAW,KAAK,IAAI,QAAQ;CAClC,MAAM,OAAO,QAAQ,KAAK,MAAM,WAAW,GAAG,CAAC;CAC/C,MAAM,SAAS,QAAQ,WAAW,GAAG;AACrC,MAAK,QAAQ,WAAW,KAAM,SAAQ,EAAE,KAAK,EAAE,KAAK;AACpD,SAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;AACjC;AAED,SAAS,uBACPC,WACAC,IACyC;CACzC,MAAM,QAAQ,UAAU,cAAc,IAAI,KAAK,IAAI;CACnD,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,IAAI,MAAM;CACV,IAAI,OAAO;CACX,IAAI,SAAS;CACb,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;UAC7B,KAAK,SAAS,QAAS,SAAQ,KAAK;UACpC,KAAK,SAAS,MAAO,OAAM,KAAK;UAChC,KAAK,SAAS,OAAQ,QAAO,KAAK;UAClC,KAAK,SAAS,SAAU,UAAS,KAAK;UACtC,KAAK,SAAS,SAAU,UAAS,KAAK;AAEjD,QAAO;EAAE;EAAM;EAAO;EAAK;EAAM;EAAQ;CAAQ;AAClD;AAED,SAAS,aAAaA,IAAYC,QAAmC;CACnE,MAAM,IAAI,IAAI,KAAK;CACnB,MAAM,KAAK,SAAS,EAAE,oBAAoB,CAAC;AAE3C,KAAI,OAAO,SAAS,MAClB,QAAO;EACL,OAAO,EAAE,EAAE,gBAAgB,CAAC;EAC5B,OAAO,QAAQ,EAAE,aAAa,GAAG,EAAE;EACnC,KAAK,QAAQ,EAAE,YAAY,CAAC;EAC5B,MAAM,QAAQ,EAAE,aAAa,CAAC;EAC9B,QAAQ,QAAQ,EAAE,eAAe,CAAC;EAClC,QAAQ,QAAQ,EAAE,eAAe,CAAC;EAClC;EACA,eAAe;CAChB;AAGH,KAAI,OAAO,SAAS,QAClB,QAAO;EACL,OAAO,EAAE,EAAE,aAAa,CAAC;EACzB,OAAO,QAAQ,EAAE,UAAU,GAAG,EAAE;EAChC,KAAK,QAAQ,EAAE,SAAS,CAAC;EACzB,MAAM,QAAQ,EAAE,UAAU,CAAC;EAC3B,QAAQ,QAAQ,EAAE,YAAY,CAAC;EAC/B,QAAQ,QAAQ,EAAE,YAAY,CAAC;EAC/B;EACA,gBAAgB,EAAE,mBAAmB;CACtC;AAGH,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,UAAU,IAAI,KAAK,KAAK,OAAO,UAAU;AAC/C,SAAO;GACL,OAAO,EAAE,QAAQ,gBAAgB,CAAC;GAClC,OAAO,QAAQ,QAAQ,aAAa,GAAG,EAAE;GACzC,KAAK,QAAQ,QAAQ,YAAY,CAAC;GAClC,MAAM,QAAQ,QAAQ,aAAa,CAAC;GACpC,QAAQ,QAAQ,QAAQ,eAAe,CAAC;GACxC,QAAQ,QAAQ,QAAQ,eAAe,CAAC;GACxC;GACA,eAAe,OAAO;EACvB;CACF;CAED,MAAM,QAAQ,uBAAuB,OAAO,WAAW,GAAG;CAC1D,MAAM,QAAQ,KAAK,IACjB,OAAO,MAAM,KAAK,EAClB,OAAO,MAAM,MAAM,GAAG,GACtB,OAAO,MAAM,IAAI,EACjB,OAAO,MAAM,KAAK,EAClB,OAAO,MAAM,OAAO,EACpB,OAAO,MAAM,OAAO,EACpB,EAAE,oBAAoB,CACvB;CACD,MAAM,gBAAgB,KAAK,OAAO,QAAQ,MAAM,IAAO;AACvD,QAAO;EAAE,GAAG;EAAO;EAAI;CAAe;AACvC;AAED,SAAS,gBAAgBC,UAAqD;AAC5E,YAAW,aAAa,YAAa,QAAO,EAAE,MAAM,MAAO;AAC3D,KAAI,aAAa,KAAM,QAAO,EAAE,MAAM,QAAS;CAE/C,MAAM,cAAc,mBAAmB,KAAK,SAAS;AACrD,KAAI,eAAe,MAAM;EACvB,MAAM,OAAO,YAAY,OAAO,MAAM,KAAK;EAC3C,MAAM,QAAQ,OAAO,YAAY,GAAG;EACpC,MAAM,UAAU,OAAO,YAAY,GAAG;AACtC,SAAO;GAAE,MAAM;GAAU,SAAS,QAAQ,QAAQ,KAAK;EAAU;CAClE;AAED,YACS,SAAS,sBAAsB,KAAK,mBAAmB,WAE9D,OAAM,IAAI,WACP,2BACC,KAAK,UAAU,SAAS,CACzB;AAIL,KAAI;AACF,SAAO;GACL,MAAM;GACN,WAAW,IAAI,KAAK,eAAe,SAAS;IAC1C;IACA,QAAQ;IACR,WAAW;IACX,MAAM;IACN,OAAO;IACP,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;GACT;EACF;CACF,QAAO;AACN,QAAM,IAAI,WACP,2BACC,KAAK,UAAU,SAAS,CACzB;CAEJ;AACF;AAED,SAAS,yBACPC,SACAC,UAC+B;AAC/B,KAAI,YAAY,OAAQ,QAAO,MAAM;AACrC,KAAI,YAAY,aAAa,SAAS,SAAS,MAC7C,QAAO,CAACJ,OAAuB,IAAI,KAAK,IAAI,aAAa;AAG3D,QAAO,CAACA,OAAuB;EAC7B,MAAM,QAAQ,aAAa,IAAI,SAAS;EACxC,MAAM,QAAQ,EAAE,MAAM,KAAK,GAAG,MAAM,MAAM,GAAG,MAAM,IAAI;EACvD,MAAM,QAAQ,EAAE,MAAM,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,GAAG,MAAM,GAAG;EACvE,MAAM,SAAS,aAAa,MAAM,eAAe,KAAK;EACtD,MAAM,UAAU,aAAa,MAAM,eAAe,MAAM;AAExD,MAAI,YAAY,qBAAsB,SAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO;AACvE,MAAI,YAAY,eAAgB,SAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ;AAClE,MAAI,YAAY,YAAa,SAAQ,EAAE,KAAK,GAAG,KAAK;AACpD,MAAI,YAAY,gBAAiB,SAAQ,EAAE,KAAK,GAAG,OAAO;AAC1D,MAAI,YAAY,UAAW,SAAQ,EAAE,KAAK,GAAG,QAAQ;AACrD,MAAI,YAAY,OAAQ,QAAO;AAC/B,MAAI,YAAY,OAAQ,QAAO;AAC/B,UAAQ,EAAE,KAAK,GAAG,KAAK,EAAE,OAAO;CACjC;AACF;AAGD,MAAM,sBAAsB;CAC1B,MAAM;CACN,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,GAAG;EACD,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;CACD,GAAG;EACD,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;CACR;AACF;;;;;;AAOD,SAAS,mBAAmBK,YAAoC;AAC9D,QAAO,eAAe,SAAS,SAAS;AACzC;AAED,SAAS,aAAaC,MAAcC,OAAyB;AAC3D,OAAM,iBAAiB,OAAQ,QAAO;CAEtC,MAAMC,aAAsC;EAC1C,MAAM,MAAM;EACZ,SAAS,MAAM;CAChB;AAED,YAAW,MAAM,UAAU,SACzB,YAAW,QAAQ,MAAM;CAG3B,MAAM,QAAS,MAA8B;AAC7C,KAAI,iBACF,YAAW,QAAQ;AAGrB,YACS,mBAAmB,eAC1B,iBAAiB,eAEjB,YAAW,SAAS,MAAM;AAG5B,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAClC,OAAM,OAAO,YACX,YAAW,OAAQ,MAA6C;AAIpE,QAAO;AACR;;;;;;;;;;;;;;;;;;AAmBD,SAAgB,iBACdC,UAAgC,CAAE,GACnB;CAEf,MAAM,oBAAoB,CAAC,MAAM;EAC/B,MAAM,WAAW,QAAQ;EACzB,MAAM,WAAW,gBAAgB,QAAQ,SAAS;AAClD,MAAI,YAAY,KACd,QAAO,yBAAyB,sBAAsB,SAAS;WACtD,aAAa,WACtB,QAAO,yBAAyB,QAAQ,SAAS;kBAE1C,aAAa,aAElB,aAAa,wBACb,aAAa,kBACb,aAAa,eACb,aAAa,mBACb,aAAa,aACb,aAAa,UACb,aAAa,UACb,aAAa,aACb,aAAa,QAGf,QAAO,yBAAyB,UAAU,SAAS;MAEnD,QAAO;CAEV,IAAG;CAEJ,MAAM,oBAAoB,QAAQ,YAAY;CAC9C,MAAM,gBAAgB,QAAQ,QAC1B,CAACC,MAAe,QAAQ,MAAO,GAAG,QAAQ,GAC1C;CAGJ,MAAM,gBAAgB,CAAC,MAAM;EAC3B,MAAM,cAAc,QAAQ;AAC5B,MAAI,eAAe,QAAQ,gBAAgB,OACzC,QAAO,CAACC,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,OACzB,QAAO,CAACA,UAA4B,oBAAoB,KAAK;WACpD,gBAAgB,IACzB,QAAO,CAACA,UAA4B,oBAAoB,EAAE;WACjD,gBAAgB,IACzB,QAAO,CAACA,UAA4B,oBAAoB,EAAE;MAE1D,QAAO;CAEV,IAAG;CAEJ,MAAM,aAAa,mBAAmB,QAAQ,WAAW;CAEzD,MAAMC,YAAiD,QAAQ,WAC5D,CAAC,EAAE,WAAW,OAAO,UAAU,SAA0B,MACvD,EAAE,aAAa,EAAE,UAAU,KAAK,GAAG,GAAG,MAAM,IAAI,SAAS,IAAI,QAAQ;AAE1E,QAAO,CAACC,WAA8B;EAEpC,MAAM,WAAW,OAAO;EACxB,MAAM,SAAS,SAAS;EAExB,IAAIC;AACJ,MAAI,WAAW,EAEb,WAAU,SAAS;WACV,UAAU,GAAG;AAEtB,aAAU;AACV,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,YAAY,IAAI,MAAM,IAAK,SAAS,KAAK,cAAc,SAAS,GAAG;EAEtE,OAAM;GAEL,MAAMC,QAAkB,IAAI,MAAM;AAClC,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,OAAM,KAAM,IAAI,MAAM,IAClB,SAAS,KACT,cAAc,SAAS,GAAG;AAEhC,aAAU,MAAM,KAAK,GAAG;EACzB;EAED,MAAM,YAAY,kBAAkB,OAAO,UAAU;EACrD,MAAM,QAAQ,cAAc,OAAO,MAAM;EACzC,MAAM,kBAAkB,sBAAsB,aAC1C,kBAAkB,OAAO,SAAS,GAClC,OAAO,SAAS,KAAK,kBAAkB;EAE3C,MAAMC,SAA0B;GAC9B;GACA;GACA;GACA;GACA;EACD;AACD,UAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,WAAW;CAC1C;AACF;;;;;;;;;;;AAYD,MAAaC,uBAAsC,kBAAkB;AAErE,MAAM,QAAQ;AAgBd,MAAMC,aAAwC;CAC5C,OAAO;CACP,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,SAAS;CACT,MAAM;CACN,OAAO;AACR;AAaD,MAAMC,aAAwC;CAC5C,MAAM;CACN,KAAK;CACL,QAAQ;CACR,WAAW;CACX,eAAe;AAChB;AAED,MAAMC,qBAAyD;CAC7D,OAAO;CACP,OAAO;CACP,MAAM;CACN,SAAS;CACT,OAAO;CACP,OAAO;AACR;;;;;;;;;AA2FD,SAAgB,sBACdC,UAAqC,CAAE,GACxB;CACf,MAAM,SAAS,QAAQ;CACvB,MAAM,wBAAwB,QAAQ,mBAAmB,cACrD,QACA,QAAQ;CACZ,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,mBAAmB,EACvB,kBAAkB,OAAO,KAAK,WAAW,gBAC1C,EAAE,kBAAkB,OAAO,KAAK,WAAW,gBAAgB;CAC5D,MAAM,kBAAkB,kBAAkB,QAAQ,kBAAkB,OAChE,KACA;CACJ,MAAM,oBAAoB,QAAQ,eAAe,cAC7C,SACA,QAAQ;CACZ,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,uBAAuB,QAAQ,kBAAkB,cACnD,QACA,QAAQ;CACZ,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,kBAAkB,EACtB,iBAAiB,OAAO,KAAK,WAAW,eACzC,EAAE,iBAAiB,OAAO,KAAK,WAAW,eAAe;CAC1D,MAAM,iBAAiB,iBAAiB,QAAQ,iBAAiB,OAC7D,KACA;AACJ,QAAO,iBAAiB;EACtB,WAAW;EACX,MAAMd,OAAgB,iBAAyB;AAC7C,UAAO,gBAAgB,OAAO,EAAE,QAAQ,KAAM,EAAC;EAChD;EACD,GAAG;EACH,OAAO,EAAE,WAAW,OAAO,UAAU,SAAS,QAAQ,EAAU;GAC9D,MAAM,aAAa,YAAY,OAAO;AACtC,eAAY,aAAa,OACrB,QACC,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB;AACrD,YAAS,EAAE,cAAc,OAAO,KAAK,WAAW,YAAY,EAC1D,cAAc,OAAO,KAAK,WAAW,YACtC,EAAE,MAAM,EAAE,cAAc,QAAQ,cAAc,OAAO,KAAK,MAAM;AACjE,UAAO,UAAU,QACZ,EACD,aAAa,OAAO,MAAM,EAAE,UAAU,GACvC,EAAE,MAAM,GAAG,eAAe,EAAE,SAAS,GAAG,eAAe,GAAG,QAAQ,IACjE,OAAO;IACP;IACA;IACA,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe;IACxD;IACA;GACD,EAAC;EACL;CACF,EAAC;AACH;;;;;;;;;;AAWD,MAAae,qBAAoC,uBAAuB;;;;;;;;;;;;;;;;AAuExE,SAAgB,sBACdC,UAAqC,CAAE,GACxB;CACf,MAAM,aAAa,mBAAmB,QAAQ,WAAW;AAGzD,MAAK,QAAQ,sBAAsB,QAAQ,YAAY,QAAQ,WAE7D,QAAO,CAACV,WAA8B;AAEpC,MAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YACpB,SACA,OAAO,MAAM,aAAa;GAC9B,SAAS,OAAO,QAAQ,KAAK,KAAK,UAAU,OAAO,QAAQ,GAAG,GAC5D,OAAO,QAAQ;GACjB,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;AAIrB,MAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YACpB,SACA,OAAO,MAAM,aAAa;GAC9B,SAAS,OAAO,QAAQ;GACxB,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;EAIrB,IAAI,MAAM,OAAO,QAAQ;AACzB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IACzC,QAAQ,IAAI,IAAK,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG,OAAO,QAAQ;AAGtE,SAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YAAY,SAAS,OAAO,MAAM,aAAa;GACvE,SAAS;GACT,QAAQ,OAAO,SAAS,KAAK,IAAI;GACjC,YAAY,OAAO;EACpB,GAAE,aAAa,GAAG;CACpB;CAIH,MAAM,oBAAoB,QAAQ,YAAY;CAC9C,MAAM,mBAAmB,QAAQ,cAAc;CAG/C,IAAIW;AACJ,YAAW,QAAQ,sBAAsB,WACvC,gBAAe,QAAQ;MAClB;EACL,MAAM,YAAY,QAAQ,qBAAqB;AAC/C,iBAAe,CAACC,aACd,SAAS,KAAK,UAAU;CAC3B;CAGD,IAAIC;AAIJ,KAAI,qBAAqB,UACvB,iBAAgB,CAAC,eAAe;UACvB,iBAAiB,WAAW,WAAW,EAAE;EAClD,MAAM,SAAS,iBAAiB,UAAU,EAAE;AAC5C,MAAI,WAAW,GACb,OAAM,IAAI,WACP,6BACC,KAAK,UAAU,iBAAiB,CACjC;AAGL,kBAAgB,CAAC,eAAe;GAC9B,MAAMC,SAAkC,CAAE;AAC1C,QAAK,MAAM,OAAO,WAChB,SAAQ,EAAE,OAAO,EAAE,IAAI,KAAK,WAAW;AAEzC,UAAO;EACR;CACF,WAAU,iBAAiB,WAAW,QAAQ,EAAE;EAC/C,MAAM,MAAM,iBAAiB,UAAU,EAAE;AACzC,kBAAgB,CAAC,gBAAgB,GAAG,MAAM,WAAY;CACvD,MACC,OAAM,IAAI,WACP,6BACC,KAAK,UAAU,iBAAiB,CACjC;CAKL,IAAIC;AAEJ,KAAI,kBACF,cAAa,CAACf,WAA8B;AAC1C,aAAW,OAAO,eAAe,SAC/B,QAAO,OAAO;EAEhB,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,WAAW,QAAQ,KAAK;AACjD,OAAI,IAAI,EAAG,QAAO;AAClB,UAAO,OAAO,WAAW;EAC1B;AACD,SAAO;CACR;KAED,cAAa,CAACA,WAA8B;EAC1C,MAAM,SAAS,OAAO,QAAQ;AAE9B,MAAI,WAAW,EACb,QAAO,OAAO,QAAQ;EAGxB,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,IAC1B,QAAQ,IAAI,IAAI,IACZ,OAAO,QAAQ,KACf,KAAK,UAAU,OAAO,QAAQ,GAAG;AAEvC,SAAO;CACR;AAGH,QAAO,CAACA,WAA8B;AACpC,SAAO,KAAK,UAAU;GACpB,cAAc,IAAI,KAAK,OAAO,WAAW,aAAa;GACtD,OAAO,OAAO,UAAU,YAAY,SAAS,OAAO,MAAM,aAAa;GACvE,SAAS,WAAW,OAAO;GAC3B,QAAQ,aAAa,OAAO,SAAS;GACrC,GAAG,cAAc,OAAO,WAAW;EACpC,GAAE,aAAa,GAAG;CACpB;AACF;;;;;;;;;;;;;;;;;AAkBD,MAAagB,qBAAoC,uBAAuB;;;;AAexE,MAAMC,iBAA2C;CAC/C,SAAS;CACT,SAAS;CACT,QAAQ;CACR,WAAW;CACX,SAAS;CACT,SAAS;AACV;;;;;;;;AASD,SAAgB,wBAAwBjB,QAAuC;CAC7E,IAAI,MAAM;CACV,MAAMkB,SAAoB,CAAE;AAC5B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IACzC,KAAI,IAAI,MAAM,EAAG,QAAO,OAAO,QAAQ;MAClC;AACH,SAAO;AACP,SAAO,KAAK,OAAO,QAAQ,GAAG;CAC/B;CAEH,MAAM,OAAO,IAAI,KAAK,OAAO;CAC7B,MAAM,QAAQ,EAAE,KAAK,aAAa,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GAC7D,KAAK,eAAe,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CACjD,GAAG,KAAK,eAAe,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GACnD,KAAK,oBAAoB,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CACtD;AACD,QAAO;GACJ,IAAI,KAAK,KAAK,mBAAmB,OAAO,OAAO,OAC9C,OAAO,SAAS,KAAK,IAAO,CAC7B,KAAK,IAAI;EACV;EACA,eAAe,OAAO;EACtB;EACA;EACA;EACA,GAAG;CACJ;AACF"}
|
package/dist/sink.cjs
CHANGED
|
@@ -377,6 +377,7 @@ function fingersCrossed(sink, options = {}) {
|
|
|
377
377
|
} else {
|
|
378
378
|
const buffers = /* @__PURE__ */ new Map();
|
|
379
379
|
const triggered = /* @__PURE__ */ new Set();
|
|
380
|
+
let accessCounter = 0;
|
|
380
381
|
let cleanupTimer = null;
|
|
381
382
|
if (hasTtl) cleanupTimer = setInterval(() => {
|
|
382
383
|
cleanupExpiredBuffers(buffers);
|
|
@@ -418,7 +419,6 @@ function fingersCrossed(sink, options = {}) {
|
|
|
418
419
|
sink(record);
|
|
419
420
|
} else if (bufferLevel != null && require_level.compareLogLevel(record.level, bufferLevel) > 0) sink(record);
|
|
420
421
|
else {
|
|
421
|
-
const now = Date.now();
|
|
422
422
|
let metadata = buffers.get(bufferKey);
|
|
423
423
|
if (!metadata) {
|
|
424
424
|
if (hasLru && buffers.size >= maxContexts) {
|
|
@@ -427,10 +427,10 @@ function fingersCrossed(sink, options = {}) {
|
|
|
427
427
|
}
|
|
428
428
|
metadata = {
|
|
429
429
|
buffer: [],
|
|
430
|
-
lastAccess:
|
|
430
|
+
lastAccess: ++accessCounter
|
|
431
431
|
};
|
|
432
432
|
buffers.set(bufferKey, metadata);
|
|
433
|
-
} else metadata.lastAccess =
|
|
433
|
+
} else metadata.lastAccess = ++accessCounter;
|
|
434
434
|
metadata.buffer.push(record);
|
|
435
435
|
while (metadata.buffer.length > maxBufferSize) metadata.buffer.shift();
|
|
436
436
|
}
|
package/dist/sink.js
CHANGED
|
@@ -377,6 +377,7 @@ function fingersCrossed(sink, options = {}) {
|
|
|
377
377
|
} else {
|
|
378
378
|
const buffers = /* @__PURE__ */ new Map();
|
|
379
379
|
const triggered = /* @__PURE__ */ new Set();
|
|
380
|
+
let accessCounter = 0;
|
|
380
381
|
let cleanupTimer = null;
|
|
381
382
|
if (hasTtl) cleanupTimer = setInterval(() => {
|
|
382
383
|
cleanupExpiredBuffers(buffers);
|
|
@@ -418,7 +419,6 @@ function fingersCrossed(sink, options = {}) {
|
|
|
418
419
|
sink(record);
|
|
419
420
|
} else if (bufferLevel != null && compareLogLevel(record.level, bufferLevel) > 0) sink(record);
|
|
420
421
|
else {
|
|
421
|
-
const now = Date.now();
|
|
422
422
|
let metadata = buffers.get(bufferKey);
|
|
423
423
|
if (!metadata) {
|
|
424
424
|
if (hasLru && buffers.size >= maxContexts) {
|
|
@@ -427,10 +427,10 @@ function fingersCrossed(sink, options = {}) {
|
|
|
427
427
|
}
|
|
428
428
|
metadata = {
|
|
429
429
|
buffer: [],
|
|
430
|
-
lastAccess:
|
|
430
|
+
lastAccess: ++accessCounter
|
|
431
431
|
};
|
|
432
432
|
buffers.set(bufferKey, metadata);
|
|
433
|
-
} else metadata.lastAccess =
|
|
433
|
+
} else metadata.lastAccess = ++accessCounter;
|
|
434
434
|
metadata.buffer.push(record);
|
|
435
435
|
while (metadata.buffer.length > maxBufferSize) metadata.buffer.shift();
|
|
436
436
|
}
|
package/dist/sink.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sink.js","names":["sink: Sink","filter: FilterLike","record: LogRecord","stream: WritableStream","options: StreamSinkOptions","sink: Sink & AsyncDisposable","buffer: LogRecord[]","flushTimer: ReturnType<typeof setInterval> | null","activeFlush: Promise<void> | null","nonBlockingSink: Sink & AsyncDisposable","options: ConsoleSinkOptions","levelMap: Record<LogLevel, ConsoleMethod>","nonBlockingSink: Sink & Disposable","asyncSink: AsyncSink","options: FingersCrossedOptions","parent: readonly string[]","child: readonly string[]","shouldFlushBuffer:\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean)\n | null","category: readonly string[]","key: string","properties: Record<string, unknown>","contextValues: Record<string, unknown>","buffers: Map<string, BufferMetadata>","expiredKeys: string[]","numToEvict?: number","cleanupTimer: ReturnType<typeof setInterval> | null","allRecordsToFlush: LogRecord[]"],"sources":["../src/sink.ts"],"sourcesContent":["import { type FilterLike, toFilter } from \"./filter.ts\";\nimport {\n type ConsoleFormatter,\n defaultConsoleFormatter,\n defaultTextFormatter,\n type TextFormatter,\n} from \"./formatter.ts\";\nimport { compareLogLevel, type LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A sink is a function that accepts a log record and prints it somewhere.\n * Thrown exceptions will be suppressed and then logged to the meta logger,\n * a {@link Logger} with the category `[\"logtape\", \"meta\"]`. (In that case,\n * the meta log record will not be passed to the sink to avoid infinite\n * recursion.)\n *\n * @param record The log record to sink.\n */\nexport type Sink = (record: LogRecord) => void;\n\n/**\n * An async sink is a function that accepts a log record and asynchronously\n * processes it. This type is used with {@link fromAsyncSink} to create\n * a regular sink that properly handles asynchronous operations.\n *\n * @param record The log record to process asynchronously.\n * @returns A promise that resolves when the record has been processed.\n * @since 1.0.0\n */\nexport type AsyncSink = (record: LogRecord) => Promise<void>;\n\n/**\n * Turns a sink into a filtered sink. The returned sink only logs records that\n * pass the filter.\n *\n * @example Filter a console sink to only log records with the info level\n * ```typescript\n * const sink = withFilter(getConsoleSink(), \"info\");\n * ```\n *\n * @param sink A sink to be filtered.\n * @param filter A filter to apply to the sink. It can be either a filter\n * function or a {@link LogLevel} string.\n * @returns A sink that only logs records that pass the filter.\n */\nexport function withFilter(sink: Sink, filter: FilterLike): Sink {\n const filterFunc = toFilter(filter);\n return (record: LogRecord) => {\n if (filterFunc(record)) sink(record);\n };\n}\n\n/**\n * Options for the {@link getStreamSink} function.\n */\nexport interface StreamSinkOptions {\n /**\n * The text formatter to use. Defaults to {@link defaultTextFormatter}.\n */\n formatter?: TextFormatter;\n\n /**\n * The text encoder to use. Defaults to an instance of {@link TextEncoder}.\n */\n encoder?: { encode(text: string): Uint8Array };\n\n /**\n * Enable non-blocking mode with optional buffer configuration.\n * When enabled, log records are buffered and flushed in the background.\n *\n * @example Simple non-blocking mode\n * ```typescript\n * getStreamSink(stream, { nonBlocking: true });\n * ```\n *\n * @example Custom buffer configuration\n * ```typescript\n * getStreamSink(stream, {\n * nonBlocking: {\n * bufferSize: 1000,\n * flushInterval: 50\n * }\n * });\n * ```\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean | {\n /**\n * Maximum number of records to buffer before flushing.\n * @default `100`\n */\n bufferSize?: number;\n\n /**\n * Interval in milliseconds between automatic flushes.\n * @default `100`\n */\n flushInterval?: number;\n };\n}\n\n/**\n * A factory that returns a sink that writes to a {@link WritableStream}.\n *\n * Note that the `stream` is of Web Streams API, which is different from\n * Node.js streams. You can convert a Node.js stream to a Web Streams API\n * stream using [`stream.Writable.toWeb()`] method.\n *\n * [`stream.Writable.toWeb()`]: https://nodejs.org/api/stream.html#streamwritabletowebstreamwritable\n *\n * @example Sink to the standard error in Deno\n * ```typescript\n * const stderrSink = getStreamSink(Deno.stderr.writable);\n * ```\n *\n * @example Sink to the standard error in Node.js\n * ```typescript\n * import stream from \"node:stream\";\n * const stderrSink = getStreamSink(stream.Writable.toWeb(process.stderr));\n * ```\n *\n * @param stream The stream to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the stream.\n */\nexport function getStreamSink(\n stream: WritableStream,\n options: StreamSinkOptions = {},\n): Sink & AsyncDisposable {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const writer = stream.getWriter();\n\n if (!options.nonBlocking) {\n let lastPromise = Promise.resolve();\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n const bytes = encoder.encode(formatter(record));\n lastPromise = lastPromise\n .then(() => writer.ready)\n .then(() => writer.write(bytes));\n };\n sink[Symbol.asyncDispose] = async () => {\n await lastPromise;\n await writer.close();\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const nonBlockingConfig = options.nonBlocking === true\n ? {}\n : options.nonBlocking;\n const bufferSize = nonBlockingConfig.bufferSize ?? 100;\n const flushInterval = nonBlockingConfig.flushInterval ?? 100;\n\n const buffer: LogRecord[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n const maxBufferSize = bufferSize * 2; // Overflow protection\n\n async function flush(): Promise<void> {\n if (buffer.length === 0) return;\n\n const records = buffer.splice(0);\n for (const record of records) {\n try {\n const bytes = encoder.encode(formatter(record));\n await writer.ready;\n await writer.write(bytes);\n } catch {\n // Silently ignore errors in non-blocking mode to avoid disrupting the application\n }\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush) return;\n\n activeFlush = flush().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Buffer overflow protection: drop oldest records if buffer is too large\n if (buffer.length >= maxBufferSize) {\n buffer.shift(); // Remove oldest record\n }\n\n buffer.push(record);\n\n if (buffer.length >= bufferSize) {\n scheduleFlush();\n } else if (flushTimer === null) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flush();\n try {\n await writer.close();\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n\ntype ConsoleMethod = \"debug\" | \"info\" | \"log\" | \"warn\" | \"error\";\n\n/**\n * Options for the {@link getConsoleSink} function.\n */\nexport interface ConsoleSinkOptions {\n /**\n * The console formatter or text formatter to use.\n * Defaults to {@link defaultConsoleFormatter}.\n */\n formatter?: ConsoleFormatter | TextFormatter;\n\n /**\n * The mapping from log levels to console methods. Defaults to:\n *\n * ```typescript\n * {\n * trace: \"trace\",\n * debug: \"debug\",\n * info: \"info\",\n * warning: \"warn\",\n * error: \"error\",\n * fatal: \"error\",\n * }\n * ```\n * @since 0.9.0\n */\n levelMap?: Record<LogLevel, ConsoleMethod>;\n\n /**\n * The console to log to. Defaults to {@link console}.\n */\n console?: Console;\n\n /**\n * Enable non-blocking mode with optional buffer configuration.\n * When enabled, log records are buffered and flushed in the background.\n *\n * @example Simple non-blocking mode\n * ```typescript\n * getConsoleSink({ nonBlocking: true });\n * ```\n *\n * @example Custom buffer configuration\n * ```typescript\n * getConsoleSink({\n * nonBlocking: {\n * bufferSize: 1000,\n * flushInterval: 50\n * }\n * });\n * ```\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean | {\n /**\n * Maximum number of records to buffer before flushing.\n * @default `100`\n */\n bufferSize?: number;\n\n /**\n * Interval in milliseconds between automatic flushes.\n * @default `100`\n */\n flushInterval?: number;\n };\n}\n\n/**\n * A console sink factory that returns a sink that logs to the console.\n *\n * @param options The options for the sink.\n * @returns A sink that logs to the console. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link Disposable}.\n */\nexport function getConsoleSink(\n options: ConsoleSinkOptions = {},\n): Sink | (Sink & Disposable) {\n const formatter = options.formatter ?? defaultConsoleFormatter;\n const levelMap: Record<LogLevel, ConsoleMethod> = {\n trace: \"debug\",\n debug: \"debug\",\n info: \"info\",\n warning: \"warn\",\n error: \"error\",\n fatal: \"error\",\n ...(options.levelMap ?? {}),\n };\n const console = options.console ?? globalThis.console;\n\n const baseSink = (record: LogRecord) => {\n const args = formatter(record);\n const method = levelMap[record.level];\n if (method === undefined) {\n throw new TypeError(`Invalid log level: ${record.level}.`);\n }\n if (typeof args === \"string\") {\n const msg = args.replace(/\\r?\\n$/, \"\");\n console[method](msg);\n } else {\n console[method](...args);\n }\n };\n\n if (!options.nonBlocking) {\n return baseSink;\n }\n\n // Non-blocking mode implementation\n const nonBlockingConfig = options.nonBlocking === true\n ? {}\n : options.nonBlocking;\n const bufferSize = nonBlockingConfig.bufferSize ?? 100;\n const flushInterval = nonBlockingConfig.flushInterval ?? 100;\n\n const buffer: LogRecord[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let flushScheduled = false;\n const maxBufferSize = bufferSize * 2; // Overflow protection\n\n function flush(): void {\n if (buffer.length === 0) return;\n\n const records = buffer.splice(0);\n for (const record of records) {\n try {\n baseSink(record);\n } catch {\n // Silently ignore errors in non-blocking mode to avoid disrupting the application\n }\n }\n }\n\n function scheduleFlush(): void {\n if (flushScheduled) return;\n\n flushScheduled = true;\n setTimeout(() => {\n flushScheduled = false;\n flush();\n }, 0);\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n flush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & Disposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Buffer overflow protection: drop oldest records if buffer is too large\n if (buffer.length >= maxBufferSize) {\n buffer.shift(); // Remove oldest record\n }\n\n buffer.push(record);\n\n if (buffer.length >= bufferSize) {\n scheduleFlush();\n } else if (flushTimer === null) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.dispose] = () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n flush();\n };\n\n return nonBlockingSink;\n}\n\n/**\n * Converts an async sink into a regular sink with proper async handling.\n * The returned sink chains async operations to ensure proper ordering and\n * implements AsyncDisposable to wait for all pending operations on disposal.\n *\n * @example Create a sink that asynchronously posts to a webhook\n * ```typescript\n * const asyncSink: AsyncSink = async (record) => {\n * await fetch(\"https://example.com/logs\", {\n * method: \"POST\",\n * body: JSON.stringify(record),\n * });\n * };\n * const sink = fromAsyncSink(asyncSink);\n * ```\n *\n * @param asyncSink The async sink function to convert.\n * @returns A sink that properly handles async operations and disposal.\n * @since 1.0.0\n */\nexport function fromAsyncSink(asyncSink: AsyncSink): Sink & AsyncDisposable {\n let lastPromise = Promise.resolve();\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n lastPromise = lastPromise\n .then(() => asyncSink(record))\n .catch(() => {\n // Errors are handled by the sink infrastructure\n });\n };\n sink[Symbol.asyncDispose] = async () => {\n await lastPromise;\n };\n return sink;\n}\n\n/**\n * Options for the {@link fingersCrossed} function.\n * @since 1.1.0\n */\nexport interface FingersCrossedOptions {\n /**\n * Minimum log level that triggers buffer flush.\n * When a log record at or above this level is received, all buffered\n * records are flushed to the wrapped sink.\n * @default `\"error\"`\n */\n readonly triggerLevel?: LogLevel;\n\n /**\n * Maximum log level that will be buffered.\n * Log records at or below this level are buffered, while records above\n * this level (but below {@link triggerLevel}) pass through immediately\n * without buffering.\n *\n * When `undefined` (default), all records below {@link triggerLevel} are\n * buffered (equivalent to setting this to the level just below triggerLevel).\n *\n * When `null`, all records below {@link triggerLevel} are buffered\n * (same as `undefined`, but explicit).\n *\n * @example Buffer only trace and debug, pass through info immediately\n * ```typescript\n * fingersCrossed(sink, {\n * bufferLevel: \"debug\", // trace, debug → buffered\n * triggerLevel: \"warning\", // warning+ → trigger flush\n * // info → passes through immediately (not buffered, not trigger)\n * })\n * ```\n *\n * @default `undefined` (buffer all levels below triggerLevel)\n * @since 2.0.0\n */\n readonly bufferLevel?: LogLevel | null;\n\n /**\n * Maximum buffer size before oldest records are dropped.\n * When the buffer exceeds this size, the oldest records are removed\n * to prevent unbounded memory growth.\n * @default `1000`\n */\n readonly maxBufferSize?: number;\n\n /**\n * Category isolation mode or custom matcher function.\n *\n * When `undefined` (default), all log records share a single buffer.\n *\n * When set to a mode string:\n *\n * - `\"descendant\"`: Flush child category buffers when parent triggers\n * - `\"ancestor\"`: Flush parent category buffers when child triggers\n * - `\"both\"`: Flush both parent and child category buffers\n *\n * When set to a function, it receives the trigger category and buffered\n * category and should return true if the buffered category should be flushed.\n *\n * @default `undefined` (no isolation, single global buffer)\n */\n readonly isolateByCategory?:\n | \"descendant\"\n | \"ancestor\"\n | \"both\"\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean);\n\n /**\n * Enable context-based buffer isolation.\n * When enabled, buffers are isolated based on specified context keys.\n * This is useful for scenarios like HTTP request tracing where logs\n * should be isolated per request.\n *\n * @example\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByContext: { keys: ['requestId'] }\n * })\n * ```\n *\n * @example Combined with category isolation\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByCategory: 'descendant',\n * isolateByContext: { keys: ['requestId', 'sessionId'] }\n * })\n * ```\n *\n * @example With TTL-based buffer cleanup\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByContext: {\n * keys: ['requestId'],\n * bufferTtlMs: 30000, // 30 seconds\n * cleanupIntervalMs: 10000 // cleanup every 10 seconds\n * }\n * })\n * ```\n *\n * @default `undefined` (no context isolation)\n * @since 1.2.0\n */\n readonly isolateByContext?: {\n /**\n * Context keys to use for isolation.\n * Buffers will be separate for different combinations of these context values.\n */\n readonly keys: readonly string[];\n\n /**\n * Maximum number of context buffers to maintain simultaneously.\n * When this limit is exceeded, the least recently used (LRU) buffers\n * will be evicted to make room for new ones.\n *\n * This provides memory protection in high-concurrency scenarios where\n * many different context values might be active simultaneously.\n *\n * When set to 0 or undefined, no limit is enforced.\n *\n * @default `undefined` (no limit)\n * @since 1.2.0\n */\n readonly maxContexts?: number;\n\n /**\n * Time-to-live for context buffers in milliseconds.\n * Buffers that haven't been accessed for this duration will be automatically\n * cleaned up to prevent memory leaks in long-running applications.\n *\n * When set to 0 or undefined, buffers will never expire based on time.\n *\n * @default `undefined` (no TTL)\n * @since 1.2.0\n */\n readonly bufferTtlMs?: number;\n\n /**\n * Interval in milliseconds for running cleanup operations.\n * The cleanup process removes expired buffers based on {@link bufferTtlMs}.\n *\n * This option is ignored if {@link bufferTtlMs} is not set.\n *\n * @default `30000` (30 seconds)\n * @since 1.2.0\n */\n readonly cleanupIntervalMs?: number;\n };\n}\n\n/**\n * Metadata for context-based buffer tracking.\n * Used internally by {@link fingersCrossed} to manage buffer lifecycle with LRU support.\n * @since 1.2.0\n */\ninterface BufferMetadata {\n /**\n * The actual log records buffer.\n */\n readonly buffer: LogRecord[];\n\n /**\n * Timestamp of the last access to this buffer (in milliseconds).\n * Used for LRU-based eviction when {@link FingersCrossedOptions.isolateByContext.maxContexts} is set.\n */\n lastAccess: number;\n}\n\n/**\n * Creates a sink that buffers log records until a trigger level is reached.\n * This pattern, known as \"fingers crossed\" logging, keeps detailed debug logs\n * in memory and only outputs them when an error or other significant event occurs.\n *\n * @example Basic usage with default settings\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink());\n * // Debug and info logs are buffered\n * // When an error occurs, all buffered logs + the error are output\n * ```\n *\n * @example Custom trigger level and buffer size\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink(), {\n * triggerLevel: \"warning\", // Trigger on warning or higher\n * maxBufferSize: 500 // Keep last 500 records\n * });\n * ```\n *\n * @example Category isolation\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink(), {\n * isolateByCategory: \"descendant\" // Separate buffers per category\n * });\n * // Error in [\"app\"] triggers flush of [\"app\"] and [\"app\", \"module\"] buffers\n * // But not [\"other\"] buffer\n * ```\n *\n * @param sink The sink to wrap. Buffered records are sent to this sink when\n * triggered.\n * @param options Configuration options for the fingers crossed behavior.\n * @returns A sink that buffers records until the trigger level is reached.\n * @since 1.1.0\n */\nexport function fingersCrossed(\n sink: Sink,\n options: FingersCrossedOptions = {},\n): Sink | (Sink & Disposable) {\n const triggerLevel = options.triggerLevel ?? \"error\";\n const bufferLevel = options.bufferLevel;\n const maxBufferSize = Math.max(0, options.maxBufferSize ?? 1000);\n const isolateByCategory = options.isolateByCategory;\n const isolateByContext = options.isolateByContext;\n\n // TTL and LRU configuration\n const bufferTtlMs = isolateByContext?.bufferTtlMs;\n const cleanupIntervalMs = isolateByContext?.cleanupIntervalMs ?? 30000;\n const maxContexts = isolateByContext?.maxContexts;\n const hasTtl = bufferTtlMs != null && bufferTtlMs > 0;\n const hasLru = maxContexts != null && maxContexts > 0;\n\n // Validate trigger level early\n try {\n compareLogLevel(\"trace\", triggerLevel); // Test with any valid level\n } catch (error) {\n throw new TypeError(\n `Invalid triggerLevel: ${JSON.stringify(triggerLevel)}. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n // Validate buffer level if provided\n if (bufferLevel != null) {\n try {\n compareLogLevel(\"trace\", bufferLevel); // Test with any valid level\n } catch (error) {\n throw new TypeError(\n `Invalid bufferLevel: ${JSON.stringify(bufferLevel)}. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n // bufferLevel must be strictly less than triggerLevel\n if (compareLogLevel(bufferLevel, triggerLevel) >= 0) {\n throw new RangeError(\n `bufferLevel (${JSON.stringify(bufferLevel)}) must be lower than ` +\n `triggerLevel (${JSON.stringify(triggerLevel)}).`,\n );\n }\n }\n\n // Helper functions for category matching\n function isDescendant(\n parent: readonly string[],\n child: readonly string[],\n ): boolean {\n if (parent.length === 0 || child.length === 0) return false; // Empty categories are isolated\n if (parent.length > child.length) return false;\n return parent.every((p, i) => p === child[i]);\n }\n\n function isAncestor(\n child: readonly string[],\n parent: readonly string[],\n ): boolean {\n if (child.length === 0 || parent.length === 0) return false; // Empty categories are isolated\n if (child.length < parent.length) return false;\n return parent.every((p, i) => p === child[i]);\n }\n\n // Determine matcher function based on isolation mode\n let shouldFlushBuffer:\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean)\n | null = null;\n\n if (isolateByCategory) {\n if (typeof isolateByCategory === \"function\") {\n shouldFlushBuffer = isolateByCategory;\n } else {\n switch (isolateByCategory) {\n case \"descendant\":\n shouldFlushBuffer = (trigger, buffered) =>\n isDescendant(trigger, buffered);\n break;\n case \"ancestor\":\n shouldFlushBuffer = (trigger, buffered) =>\n isAncestor(trigger, buffered);\n break;\n case \"both\":\n shouldFlushBuffer = (trigger, buffered) =>\n isDescendant(trigger, buffered) || isAncestor(trigger, buffered);\n break;\n }\n }\n }\n\n // Helper functions for category serialization\n function getCategoryKey(category: readonly string[]): string {\n return JSON.stringify(category);\n }\n\n function parseCategoryKey(key: string): string[] {\n return JSON.parse(key);\n }\n\n // Helper function to extract context values from properties\n function getContextKey(properties: Record<string, unknown>): string {\n if (!isolateByContext || isolateByContext.keys.length === 0) {\n return \"\";\n }\n const contextValues: Record<string, unknown> = {};\n for (const key of isolateByContext.keys) {\n if (key in properties) {\n contextValues[key] = properties[key];\n }\n }\n return JSON.stringify(contextValues);\n }\n\n // Helper function to generate buffer key\n function getBufferKey(\n category: readonly string[],\n properties: Record<string, unknown>,\n ): string {\n const categoryKey = getCategoryKey(category);\n if (!isolateByContext) {\n return categoryKey;\n }\n const contextKey = getContextKey(properties);\n return `${categoryKey}:${contextKey}`;\n }\n\n // Helper function to parse buffer key\n function parseBufferKey(key: string): {\n category: string[];\n context: string;\n } {\n if (!isolateByContext) {\n return { category: parseCategoryKey(key), context: \"\" };\n }\n // Find the separator between category and context\n // The category part is JSON-encoded, so we need to find where it ends\n // We look for \"]:\" which indicates end of category array and start of context\n const separatorIndex = key.indexOf(\"]:\");\n if (separatorIndex === -1) {\n // No context part, entire key is category\n return { category: parseCategoryKey(key), context: \"\" };\n }\n const categoryPart = key.substring(0, separatorIndex + 1); // Include the ]\n const contextPart = key.substring(separatorIndex + 2); // Skip ]:\n return { category: parseCategoryKey(categoryPart), context: contextPart };\n }\n\n // TTL-based cleanup function\n function cleanupExpiredBuffers(buffers: Map<string, BufferMetadata>): void {\n if (!hasTtl) return;\n\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, metadata] of buffers) {\n if (metadata.buffer.length === 0) continue;\n\n // Use the timestamp of the last (most recent) record in the buffer\n const lastRecordTimestamp =\n metadata.buffer[metadata.buffer.length - 1].timestamp;\n if (now - lastRecordTimestamp > bufferTtlMs!) {\n expiredKeys.push(key);\n }\n }\n\n // Remove expired buffers\n for (const key of expiredKeys) {\n buffers.delete(key);\n }\n }\n\n // LRU-based eviction function\n function evictLruBuffers(\n buffers: Map<string, BufferMetadata>,\n numToEvict?: number,\n ): void {\n if (!hasLru) return;\n\n // Use provided numToEvict or calculate based on current size vs limit\n const toEvict = numToEvict ?? Math.max(0, buffers.size - maxContexts!);\n if (toEvict <= 0) return;\n\n // Sort by lastAccess timestamp (oldest first)\n const sortedEntries = Array.from(buffers.entries())\n .sort(([, a], [, b]) => a.lastAccess - b.lastAccess);\n\n // Remove the oldest buffers\n for (let i = 0; i < toEvict; i++) {\n const [key] = sortedEntries[i];\n buffers.delete(key);\n }\n }\n\n // Buffer management\n if (!isolateByCategory && !isolateByContext) {\n // Single global buffer\n const buffer: LogRecord[] = [];\n let triggered = false;\n\n return (record: LogRecord) => {\n if (triggered) {\n // Already triggered, pass through directly\n sink(record);\n return;\n }\n\n // Check if this record triggers flush\n if (compareLogLevel(record.level, triggerLevel) >= 0) {\n triggered = true;\n\n // Flush buffer\n for (const bufferedRecord of buffer) {\n sink(bufferedRecord);\n }\n buffer.length = 0;\n\n // Send trigger record\n sink(record);\n } else if (\n bufferLevel != null &&\n compareLogLevel(record.level, bufferLevel) > 0\n ) {\n // Record is above bufferLevel but below triggerLevel: pass through\n sink(record);\n } else {\n // Buffer the record\n buffer.push(record);\n\n // Enforce max buffer size\n while (buffer.length > maxBufferSize) {\n buffer.shift();\n }\n }\n };\n } else {\n // Category and/or context-isolated buffers\n const buffers = new Map<string, BufferMetadata>();\n const triggered = new Set<string>();\n\n // Set up TTL cleanup timer if enabled\n let cleanupTimer: ReturnType<typeof setInterval> | null = null;\n if (hasTtl) {\n cleanupTimer = setInterval(() => {\n cleanupExpiredBuffers(buffers);\n }, cleanupIntervalMs);\n }\n\n const fingersCrossedSink = (record: LogRecord) => {\n const bufferKey = getBufferKey(record.category, record.properties);\n\n // Check if this buffer is already triggered\n if (triggered.has(bufferKey)) {\n sink(record);\n return;\n }\n\n // Check if this record triggers flush\n if (compareLogLevel(record.level, triggerLevel) >= 0) {\n // Find all buffers that should be flushed\n const keysToFlush = new Set<string>();\n\n for (const [bufferedKey] of buffers) {\n if (bufferedKey === bufferKey) {\n keysToFlush.add(bufferedKey);\n } else {\n const { category: bufferedCategory, context: bufferedContext } =\n parseBufferKey(bufferedKey);\n const { context: triggerContext } = parseBufferKey(bufferKey);\n\n // Check context match\n let contextMatches = true;\n if (isolateByContext) {\n contextMatches = bufferedContext === triggerContext;\n }\n\n // Check category match\n let categoryMatches = false;\n if (!isolateByCategory) {\n // No category isolation, so all categories match if context matches\n categoryMatches = contextMatches;\n } else if (shouldFlushBuffer) {\n try {\n categoryMatches = shouldFlushBuffer(\n record.category,\n bufferedCategory,\n );\n } catch {\n // Ignore errors from custom matcher\n }\n } else {\n // Same category only\n categoryMatches = getCategoryKey(record.category) ===\n getCategoryKey(bufferedCategory);\n }\n\n // Both must match for the buffer to be flushed\n if (contextMatches && categoryMatches) {\n keysToFlush.add(bufferedKey);\n }\n }\n }\n\n // Flush matching buffers\n const allRecordsToFlush: LogRecord[] = [];\n for (const key of keysToFlush) {\n const metadata = buffers.get(key);\n if (metadata) {\n allRecordsToFlush.push(...metadata.buffer);\n buffers.delete(key);\n triggered.add(key);\n }\n }\n\n // Sort by timestamp to maintain chronological order\n allRecordsToFlush.sort((a, b) => a.timestamp - b.timestamp);\n\n // Flush all records\n for (const bufferedRecord of allRecordsToFlush) {\n sink(bufferedRecord);\n }\n\n // Mark trigger buffer as triggered and send trigger record\n triggered.add(bufferKey);\n sink(record);\n } else if (\n bufferLevel != null &&\n compareLogLevel(record.level, bufferLevel) > 0\n ) {\n // Record is above bufferLevel but below triggerLevel: pass through\n sink(record);\n } else {\n // Buffer the record\n const now = Date.now();\n let metadata = buffers.get(bufferKey);\n if (!metadata) {\n // Apply LRU eviction if adding new buffer would exceed capacity\n if (hasLru && buffers.size >= maxContexts!) {\n // Calculate how many buffers to evict to make room for the new one\n const numToEvict = buffers.size - maxContexts! + 1;\n evictLruBuffers(buffers, numToEvict);\n }\n\n metadata = {\n buffer: [],\n lastAccess: now,\n };\n buffers.set(bufferKey, metadata);\n } else {\n // Update last access time for LRU\n metadata.lastAccess = now;\n }\n\n metadata.buffer.push(record);\n\n // Enforce max buffer size per buffer\n while (metadata.buffer.length > maxBufferSize) {\n metadata.buffer.shift();\n }\n }\n };\n\n // Add disposal functionality to clean up timer\n if (cleanupTimer !== null) {\n (fingersCrossedSink as Sink & Disposable)[Symbol.dispose] = () => {\n if (cleanupTimer !== null) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n }\n };\n }\n\n return fingersCrossedSink;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sink.js","names":["sink: Sink","filter: FilterLike","record: LogRecord","stream: WritableStream","options: StreamSinkOptions","sink: Sink & AsyncDisposable","buffer: LogRecord[]","flushTimer: ReturnType<typeof setInterval> | null","activeFlush: Promise<void> | null","nonBlockingSink: Sink & AsyncDisposable","options: ConsoleSinkOptions","levelMap: Record<LogLevel, ConsoleMethod>","nonBlockingSink: Sink & Disposable","asyncSink: AsyncSink","options: FingersCrossedOptions","parent: readonly string[]","child: readonly string[]","shouldFlushBuffer:\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean)\n | null","category: readonly string[]","key: string","properties: Record<string, unknown>","contextValues: Record<string, unknown>","buffers: Map<string, BufferMetadata>","expiredKeys: string[]","numToEvict?: number","cleanupTimer: ReturnType<typeof setInterval> | null","allRecordsToFlush: LogRecord[]"],"sources":["../src/sink.ts"],"sourcesContent":["import { type FilterLike, toFilter } from \"./filter.ts\";\nimport {\n type ConsoleFormatter,\n defaultConsoleFormatter,\n defaultTextFormatter,\n type TextFormatter,\n} from \"./formatter.ts\";\nimport { compareLogLevel, type LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A sink is a function that accepts a log record and prints it somewhere.\n * Thrown exceptions will be suppressed and then logged to the meta logger,\n * a {@link Logger} with the category `[\"logtape\", \"meta\"]`. (In that case,\n * the meta log record will not be passed to the sink to avoid infinite\n * recursion.)\n *\n * @param record The log record to sink.\n */\nexport type Sink = (record: LogRecord) => void;\n\n/**\n * An async sink is a function that accepts a log record and asynchronously\n * processes it. This type is used with {@link fromAsyncSink} to create\n * a regular sink that properly handles asynchronous operations.\n *\n * @param record The log record to process asynchronously.\n * @returns A promise that resolves when the record has been processed.\n * @since 1.0.0\n */\nexport type AsyncSink = (record: LogRecord) => Promise<void>;\n\n/**\n * Turns a sink into a filtered sink. The returned sink only logs records that\n * pass the filter.\n *\n * @example Filter a console sink to only log records with the info level\n * ```typescript\n * const sink = withFilter(getConsoleSink(), \"info\");\n * ```\n *\n * @param sink A sink to be filtered.\n * @param filter A filter to apply to the sink. It can be either a filter\n * function or a {@link LogLevel} string.\n * @returns A sink that only logs records that pass the filter.\n */\nexport function withFilter(sink: Sink, filter: FilterLike): Sink {\n const filterFunc = toFilter(filter);\n return (record: LogRecord) => {\n if (filterFunc(record)) sink(record);\n };\n}\n\n/**\n * Options for the {@link getStreamSink} function.\n */\nexport interface StreamSinkOptions {\n /**\n * The text formatter to use. Defaults to {@link defaultTextFormatter}.\n */\n formatter?: TextFormatter;\n\n /**\n * The text encoder to use. Defaults to an instance of {@link TextEncoder}.\n */\n encoder?: { encode(text: string): Uint8Array };\n\n /**\n * Enable non-blocking mode with optional buffer configuration.\n * When enabled, log records are buffered and flushed in the background.\n *\n * @example Simple non-blocking mode\n * ```typescript\n * getStreamSink(stream, { nonBlocking: true });\n * ```\n *\n * @example Custom buffer configuration\n * ```typescript\n * getStreamSink(stream, {\n * nonBlocking: {\n * bufferSize: 1000,\n * flushInterval: 50\n * }\n * });\n * ```\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean | {\n /**\n * Maximum number of records to buffer before flushing.\n * @default `100`\n */\n bufferSize?: number;\n\n /**\n * Interval in milliseconds between automatic flushes.\n * @default `100`\n */\n flushInterval?: number;\n };\n}\n\n/**\n * A factory that returns a sink that writes to a {@link WritableStream}.\n *\n * Note that the `stream` is of Web Streams API, which is different from\n * Node.js streams. You can convert a Node.js stream to a Web Streams API\n * stream using [`stream.Writable.toWeb()`] method.\n *\n * [`stream.Writable.toWeb()`]: https://nodejs.org/api/stream.html#streamwritabletowebstreamwritable\n *\n * @example Sink to the standard error in Deno\n * ```typescript\n * const stderrSink = getStreamSink(Deno.stderr.writable);\n * ```\n *\n * @example Sink to the standard error in Node.js\n * ```typescript\n * import stream from \"node:stream\";\n * const stderrSink = getStreamSink(stream.Writable.toWeb(process.stderr));\n * ```\n *\n * @param stream The stream to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the stream.\n */\nexport function getStreamSink(\n stream: WritableStream,\n options: StreamSinkOptions = {},\n): Sink & AsyncDisposable {\n const formatter = options.formatter ?? defaultTextFormatter;\n const encoder = options.encoder ?? new TextEncoder();\n const writer = stream.getWriter();\n\n if (!options.nonBlocking) {\n let lastPromise = Promise.resolve();\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n const bytes = encoder.encode(formatter(record));\n lastPromise = lastPromise\n .then(() => writer.ready)\n .then(() => writer.write(bytes));\n };\n sink[Symbol.asyncDispose] = async () => {\n await lastPromise;\n await writer.close();\n };\n return sink;\n }\n\n // Non-blocking mode implementation\n const nonBlockingConfig = options.nonBlocking === true\n ? {}\n : options.nonBlocking;\n const bufferSize = nonBlockingConfig.bufferSize ?? 100;\n const flushInterval = nonBlockingConfig.flushInterval ?? 100;\n\n const buffer: LogRecord[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let activeFlush: Promise<void> | null = null;\n const maxBufferSize = bufferSize * 2; // Overflow protection\n\n async function flush(): Promise<void> {\n if (buffer.length === 0) return;\n\n const records = buffer.splice(0);\n for (const record of records) {\n try {\n const bytes = encoder.encode(formatter(record));\n await writer.ready;\n await writer.write(bytes);\n } catch {\n // Silently ignore errors in non-blocking mode to avoid disrupting the application\n }\n }\n }\n\n function scheduleFlush(): void {\n if (activeFlush) return;\n\n activeFlush = flush().finally(() => {\n activeFlush = null;\n });\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n scheduleFlush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & AsyncDisposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Buffer overflow protection: drop oldest records if buffer is too large\n if (buffer.length >= maxBufferSize) {\n buffer.shift(); // Remove oldest record\n }\n\n buffer.push(record);\n\n if (buffer.length >= bufferSize) {\n scheduleFlush();\n } else if (flushTimer === null) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.asyncDispose] = async () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n await flush();\n try {\n await writer.close();\n } catch {\n // Writer might already be closed or errored\n }\n };\n\n return nonBlockingSink;\n}\n\ntype ConsoleMethod = \"debug\" | \"info\" | \"log\" | \"warn\" | \"error\";\n\n/**\n * Options for the {@link getConsoleSink} function.\n */\nexport interface ConsoleSinkOptions {\n /**\n * The console formatter or text formatter to use.\n * Defaults to {@link defaultConsoleFormatter}.\n */\n formatter?: ConsoleFormatter | TextFormatter;\n\n /**\n * The mapping from log levels to console methods. Defaults to:\n *\n * ```typescript\n * {\n * trace: \"trace\",\n * debug: \"debug\",\n * info: \"info\",\n * warning: \"warn\",\n * error: \"error\",\n * fatal: \"error\",\n * }\n * ```\n * @since 0.9.0\n */\n levelMap?: Record<LogLevel, ConsoleMethod>;\n\n /**\n * The console to log to. Defaults to {@link console}.\n */\n console?: Console;\n\n /**\n * Enable non-blocking mode with optional buffer configuration.\n * When enabled, log records are buffered and flushed in the background.\n *\n * @example Simple non-blocking mode\n * ```typescript\n * getConsoleSink({ nonBlocking: true });\n * ```\n *\n * @example Custom buffer configuration\n * ```typescript\n * getConsoleSink({\n * nonBlocking: {\n * bufferSize: 1000,\n * flushInterval: 50\n * }\n * });\n * ```\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean | {\n /**\n * Maximum number of records to buffer before flushing.\n * @default `100`\n */\n bufferSize?: number;\n\n /**\n * Interval in milliseconds between automatic flushes.\n * @default `100`\n */\n flushInterval?: number;\n };\n}\n\n/**\n * A console sink factory that returns a sink that logs to the console.\n *\n * @param options The options for the sink.\n * @returns A sink that logs to the console. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link Disposable}.\n */\nexport function getConsoleSink(\n options: ConsoleSinkOptions = {},\n): Sink | (Sink & Disposable) {\n const formatter = options.formatter ?? defaultConsoleFormatter;\n const levelMap: Record<LogLevel, ConsoleMethod> = {\n trace: \"debug\",\n debug: \"debug\",\n info: \"info\",\n warning: \"warn\",\n error: \"error\",\n fatal: \"error\",\n ...(options.levelMap ?? {}),\n };\n const console = options.console ?? globalThis.console;\n\n const baseSink = (record: LogRecord) => {\n const args = formatter(record);\n const method = levelMap[record.level];\n if (method === undefined) {\n throw new TypeError(`Invalid log level: ${record.level}.`);\n }\n if (typeof args === \"string\") {\n const msg = args.replace(/\\r?\\n$/, \"\");\n console[method](msg);\n } else {\n console[method](...args);\n }\n };\n\n if (!options.nonBlocking) {\n return baseSink;\n }\n\n // Non-blocking mode implementation\n const nonBlockingConfig = options.nonBlocking === true\n ? {}\n : options.nonBlocking;\n const bufferSize = nonBlockingConfig.bufferSize ?? 100;\n const flushInterval = nonBlockingConfig.flushInterval ?? 100;\n\n const buffer: LogRecord[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let flushScheduled = false;\n const maxBufferSize = bufferSize * 2; // Overflow protection\n\n function flush(): void {\n if (buffer.length === 0) return;\n\n const records = buffer.splice(0);\n for (const record of records) {\n try {\n baseSink(record);\n } catch {\n // Silently ignore errors in non-blocking mode to avoid disrupting the application\n }\n }\n }\n\n function scheduleFlush(): void {\n if (flushScheduled) return;\n\n flushScheduled = true;\n setTimeout(() => {\n flushScheduled = false;\n flush();\n }, 0);\n }\n\n function startFlushTimer(): void {\n if (flushTimer !== null || disposed) return;\n\n flushTimer = setInterval(() => {\n flush();\n }, flushInterval);\n }\n\n const nonBlockingSink: Sink & Disposable = (record: LogRecord) => {\n if (disposed) return;\n\n // Buffer overflow protection: drop oldest records if buffer is too large\n if (buffer.length >= maxBufferSize) {\n buffer.shift(); // Remove oldest record\n }\n\n buffer.push(record);\n\n if (buffer.length >= bufferSize) {\n scheduleFlush();\n } else if (flushTimer === null) {\n startFlushTimer();\n }\n };\n\n nonBlockingSink[Symbol.dispose] = () => {\n disposed = true;\n if (flushTimer !== null) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n flush();\n };\n\n return nonBlockingSink;\n}\n\n/**\n * Converts an async sink into a regular sink with proper async handling.\n * The returned sink chains async operations to ensure proper ordering and\n * implements AsyncDisposable to wait for all pending operations on disposal.\n *\n * @example Create a sink that asynchronously posts to a webhook\n * ```typescript\n * const asyncSink: AsyncSink = async (record) => {\n * await fetch(\"https://example.com/logs\", {\n * method: \"POST\",\n * body: JSON.stringify(record),\n * });\n * };\n * const sink = fromAsyncSink(asyncSink);\n * ```\n *\n * @param asyncSink The async sink function to convert.\n * @returns A sink that properly handles async operations and disposal.\n * @since 1.0.0\n */\nexport function fromAsyncSink(asyncSink: AsyncSink): Sink & AsyncDisposable {\n let lastPromise = Promise.resolve();\n const sink: Sink & AsyncDisposable = (record: LogRecord) => {\n lastPromise = lastPromise\n .then(() => asyncSink(record))\n .catch(() => {\n // Errors are handled by the sink infrastructure\n });\n };\n sink[Symbol.asyncDispose] = async () => {\n await lastPromise;\n };\n return sink;\n}\n\n/**\n * Options for the {@link fingersCrossed} function.\n * @since 1.1.0\n */\nexport interface FingersCrossedOptions {\n /**\n * Minimum log level that triggers buffer flush.\n * When a log record at or above this level is received, all buffered\n * records are flushed to the wrapped sink.\n * @default `\"error\"`\n */\n readonly triggerLevel?: LogLevel;\n\n /**\n * Maximum log level that will be buffered.\n * Log records at or below this level are buffered, while records above\n * this level (but below {@link triggerLevel}) pass through immediately\n * without buffering.\n *\n * When `undefined` (default), all records below {@link triggerLevel} are\n * buffered (equivalent to setting this to the level just below triggerLevel).\n *\n * When `null`, all records below {@link triggerLevel} are buffered\n * (same as `undefined`, but explicit).\n *\n * @example Buffer only trace and debug, pass through info immediately\n * ```typescript\n * fingersCrossed(sink, {\n * bufferLevel: \"debug\", // trace, debug → buffered\n * triggerLevel: \"warning\", // warning+ → trigger flush\n * // info → passes through immediately (not buffered, not trigger)\n * })\n * ```\n *\n * @default `undefined` (buffer all levels below triggerLevel)\n * @since 2.0.0\n */\n readonly bufferLevel?: LogLevel | null;\n\n /**\n * Maximum buffer size before oldest records are dropped.\n * When the buffer exceeds this size, the oldest records are removed\n * to prevent unbounded memory growth.\n * @default `1000`\n */\n readonly maxBufferSize?: number;\n\n /**\n * Category isolation mode or custom matcher function.\n *\n * When `undefined` (default), all log records share a single buffer.\n *\n * When set to a mode string:\n *\n * - `\"descendant\"`: Flush child category buffers when parent triggers\n * - `\"ancestor\"`: Flush parent category buffers when child triggers\n * - `\"both\"`: Flush both parent and child category buffers\n *\n * When set to a function, it receives the trigger category and buffered\n * category and should return true if the buffered category should be flushed.\n *\n * @default `undefined` (no isolation, single global buffer)\n */\n readonly isolateByCategory?:\n | \"descendant\"\n | \"ancestor\"\n | \"both\"\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean);\n\n /**\n * Enable context-based buffer isolation.\n * When enabled, buffers are isolated based on specified context keys.\n * This is useful for scenarios like HTTP request tracing where logs\n * should be isolated per request.\n *\n * @example\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByContext: { keys: ['requestId'] }\n * })\n * ```\n *\n * @example Combined with category isolation\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByCategory: 'descendant',\n * isolateByContext: { keys: ['requestId', 'sessionId'] }\n * })\n * ```\n *\n * @example With TTL-based buffer cleanup\n * ```typescript\n * fingersCrossed(sink, {\n * isolateByContext: {\n * keys: ['requestId'],\n * bufferTtlMs: 30000, // 30 seconds\n * cleanupIntervalMs: 10000 // cleanup every 10 seconds\n * }\n * })\n * ```\n *\n * @default `undefined` (no context isolation)\n * @since 1.2.0\n */\n readonly isolateByContext?: {\n /**\n * Context keys to use for isolation.\n * Buffers will be separate for different combinations of these context values.\n */\n readonly keys: readonly string[];\n\n /**\n * Maximum number of context buffers to maintain simultaneously.\n * When this limit is exceeded, the least recently used (LRU) buffers\n * will be evicted to make room for new ones.\n *\n * This provides memory protection in high-concurrency scenarios where\n * many different context values might be active simultaneously.\n *\n * When set to 0 or undefined, no limit is enforced.\n *\n * @default `undefined` (no limit)\n * @since 1.2.0\n */\n readonly maxContexts?: number;\n\n /**\n * Time-to-live for context buffers in milliseconds.\n * Buffers that haven't been accessed for this duration will be automatically\n * cleaned up to prevent memory leaks in long-running applications.\n *\n * When set to 0 or undefined, buffers will never expire based on time.\n *\n * @default `undefined` (no TTL)\n * @since 1.2.0\n */\n readonly bufferTtlMs?: number;\n\n /**\n * Interval in milliseconds for running cleanup operations.\n * The cleanup process removes expired buffers based on {@link bufferTtlMs}.\n *\n * This option is ignored if {@link bufferTtlMs} is not set.\n *\n * @default `30000` (30 seconds)\n * @since 1.2.0\n */\n readonly cleanupIntervalMs?: number;\n };\n}\n\n/**\n * Metadata for context-based buffer tracking.\n * Used internally by {@link fingersCrossed} to manage buffer lifecycle with LRU support.\n * @since 1.2.0\n */\ninterface BufferMetadata {\n /**\n * The actual log records buffer.\n */\n readonly buffer: LogRecord[];\n\n /**\n * Monotonically increasing order of the last access to this buffer.\n * Used for LRU-based eviction when {@link FingersCrossedOptions.isolateByContext.maxContexts} is set.\n */\n lastAccess: number;\n}\n\n/**\n * Creates a sink that buffers log records until a trigger level is reached.\n * This pattern, known as \"fingers crossed\" logging, keeps detailed debug logs\n * in memory and only outputs them when an error or other significant event occurs.\n *\n * @example Basic usage with default settings\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink());\n * // Debug and info logs are buffered\n * // When an error occurs, all buffered logs + the error are output\n * ```\n *\n * @example Custom trigger level and buffer size\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink(), {\n * triggerLevel: \"warning\", // Trigger on warning or higher\n * maxBufferSize: 500 // Keep last 500 records\n * });\n * ```\n *\n * @example Category isolation\n * ```typescript\n * const sink = fingersCrossed(getConsoleSink(), {\n * isolateByCategory: \"descendant\" // Separate buffers per category\n * });\n * // Error in [\"app\"] triggers flush of [\"app\"] and [\"app\", \"module\"] buffers\n * // But not [\"other\"] buffer\n * ```\n *\n * @param sink The sink to wrap. Buffered records are sent to this sink when\n * triggered.\n * @param options Configuration options for the fingers crossed behavior.\n * @returns A sink that buffers records until the trigger level is reached.\n * @since 1.1.0\n */\nexport function fingersCrossed(\n sink: Sink,\n options: FingersCrossedOptions = {},\n): Sink | (Sink & Disposable) {\n const triggerLevel = options.triggerLevel ?? \"error\";\n const bufferLevel = options.bufferLevel;\n const maxBufferSize = Math.max(0, options.maxBufferSize ?? 1000);\n const isolateByCategory = options.isolateByCategory;\n const isolateByContext = options.isolateByContext;\n\n // TTL and LRU configuration\n const bufferTtlMs = isolateByContext?.bufferTtlMs;\n const cleanupIntervalMs = isolateByContext?.cleanupIntervalMs ?? 30000;\n const maxContexts = isolateByContext?.maxContexts;\n const hasTtl = bufferTtlMs != null && bufferTtlMs > 0;\n const hasLru = maxContexts != null && maxContexts > 0;\n\n // Validate trigger level early\n try {\n compareLogLevel(\"trace\", triggerLevel); // Test with any valid level\n } catch (error) {\n throw new TypeError(\n `Invalid triggerLevel: ${JSON.stringify(triggerLevel)}. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n // Validate buffer level if provided\n if (bufferLevel != null) {\n try {\n compareLogLevel(\"trace\", bufferLevel); // Test with any valid level\n } catch (error) {\n throw new TypeError(\n `Invalid bufferLevel: ${JSON.stringify(bufferLevel)}. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n // bufferLevel must be strictly less than triggerLevel\n if (compareLogLevel(bufferLevel, triggerLevel) >= 0) {\n throw new RangeError(\n `bufferLevel (${JSON.stringify(bufferLevel)}) must be lower than ` +\n `triggerLevel (${JSON.stringify(triggerLevel)}).`,\n );\n }\n }\n\n // Helper functions for category matching\n function isDescendant(\n parent: readonly string[],\n child: readonly string[],\n ): boolean {\n if (parent.length === 0 || child.length === 0) return false; // Empty categories are isolated\n if (parent.length > child.length) return false;\n return parent.every((p, i) => p === child[i]);\n }\n\n function isAncestor(\n child: readonly string[],\n parent: readonly string[],\n ): boolean {\n if (child.length === 0 || parent.length === 0) return false; // Empty categories are isolated\n if (child.length < parent.length) return false;\n return parent.every((p, i) => p === child[i]);\n }\n\n // Determine matcher function based on isolation mode\n let shouldFlushBuffer:\n | ((\n triggerCategory: readonly string[],\n bufferedCategory: readonly string[],\n ) => boolean)\n | null = null;\n\n if (isolateByCategory) {\n if (typeof isolateByCategory === \"function\") {\n shouldFlushBuffer = isolateByCategory;\n } else {\n switch (isolateByCategory) {\n case \"descendant\":\n shouldFlushBuffer = (trigger, buffered) =>\n isDescendant(trigger, buffered);\n break;\n case \"ancestor\":\n shouldFlushBuffer = (trigger, buffered) =>\n isAncestor(trigger, buffered);\n break;\n case \"both\":\n shouldFlushBuffer = (trigger, buffered) =>\n isDescendant(trigger, buffered) || isAncestor(trigger, buffered);\n break;\n }\n }\n }\n\n // Helper functions for category serialization\n function getCategoryKey(category: readonly string[]): string {\n return JSON.stringify(category);\n }\n\n function parseCategoryKey(key: string): string[] {\n return JSON.parse(key);\n }\n\n // Helper function to extract context values from properties\n function getContextKey(properties: Record<string, unknown>): string {\n if (!isolateByContext || isolateByContext.keys.length === 0) {\n return \"\";\n }\n const contextValues: Record<string, unknown> = {};\n for (const key of isolateByContext.keys) {\n if (key in properties) {\n contextValues[key] = properties[key];\n }\n }\n return JSON.stringify(contextValues);\n }\n\n // Helper function to generate buffer key\n function getBufferKey(\n category: readonly string[],\n properties: Record<string, unknown>,\n ): string {\n const categoryKey = getCategoryKey(category);\n if (!isolateByContext) {\n return categoryKey;\n }\n const contextKey = getContextKey(properties);\n return `${categoryKey}:${contextKey}`;\n }\n\n // Helper function to parse buffer key\n function parseBufferKey(key: string): {\n category: string[];\n context: string;\n } {\n if (!isolateByContext) {\n return { category: parseCategoryKey(key), context: \"\" };\n }\n // Find the separator between category and context\n // The category part is JSON-encoded, so we need to find where it ends\n // We look for \"]:\" which indicates end of category array and start of context\n const separatorIndex = key.indexOf(\"]:\");\n if (separatorIndex === -1) {\n // No context part, entire key is category\n return { category: parseCategoryKey(key), context: \"\" };\n }\n const categoryPart = key.substring(0, separatorIndex + 1); // Include the ]\n const contextPart = key.substring(separatorIndex + 2); // Skip ]:\n return { category: parseCategoryKey(categoryPart), context: contextPart };\n }\n\n // TTL-based cleanup function\n function cleanupExpiredBuffers(buffers: Map<string, BufferMetadata>): void {\n if (!hasTtl) return;\n\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, metadata] of buffers) {\n if (metadata.buffer.length === 0) continue;\n\n // Use the timestamp of the last (most recent) record in the buffer\n const lastRecordTimestamp =\n metadata.buffer[metadata.buffer.length - 1].timestamp;\n if (now - lastRecordTimestamp > bufferTtlMs!) {\n expiredKeys.push(key);\n }\n }\n\n // Remove expired buffers\n for (const key of expiredKeys) {\n buffers.delete(key);\n }\n }\n\n // LRU-based eviction function\n function evictLruBuffers(\n buffers: Map<string, BufferMetadata>,\n numToEvict?: number,\n ): void {\n if (!hasLru) return;\n\n // Use provided numToEvict or calculate based on current size vs limit\n const toEvict = numToEvict ?? Math.max(0, buffers.size - maxContexts!);\n if (toEvict <= 0) return;\n\n // Sort by lastAccess timestamp (oldest first)\n const sortedEntries = Array.from(buffers.entries())\n .sort(([, a], [, b]) => a.lastAccess - b.lastAccess);\n\n // Remove the oldest buffers\n for (let i = 0; i < toEvict; i++) {\n const [key] = sortedEntries[i];\n buffers.delete(key);\n }\n }\n\n // Buffer management\n if (!isolateByCategory && !isolateByContext) {\n // Single global buffer\n const buffer: LogRecord[] = [];\n let triggered = false;\n\n return (record: LogRecord) => {\n if (triggered) {\n // Already triggered, pass through directly\n sink(record);\n return;\n }\n\n // Check if this record triggers flush\n if (compareLogLevel(record.level, triggerLevel) >= 0) {\n triggered = true;\n\n // Flush buffer\n for (const bufferedRecord of buffer) {\n sink(bufferedRecord);\n }\n buffer.length = 0;\n\n // Send trigger record\n sink(record);\n } else if (\n bufferLevel != null &&\n compareLogLevel(record.level, bufferLevel) > 0\n ) {\n // Record is above bufferLevel but below triggerLevel: pass through\n sink(record);\n } else {\n // Buffer the record\n buffer.push(record);\n\n // Enforce max buffer size\n while (buffer.length > maxBufferSize) {\n buffer.shift();\n }\n }\n };\n } else {\n // Category and/or context-isolated buffers\n const buffers = new Map<string, BufferMetadata>();\n const triggered = new Set<string>();\n let accessCounter = 0;\n\n // Set up TTL cleanup timer if enabled\n let cleanupTimer: ReturnType<typeof setInterval> | null = null;\n if (hasTtl) {\n cleanupTimer = setInterval(() => {\n cleanupExpiredBuffers(buffers);\n }, cleanupIntervalMs);\n }\n\n const fingersCrossedSink = (record: LogRecord) => {\n const bufferKey = getBufferKey(record.category, record.properties);\n\n // Check if this buffer is already triggered\n if (triggered.has(bufferKey)) {\n sink(record);\n return;\n }\n\n // Check if this record triggers flush\n if (compareLogLevel(record.level, triggerLevel) >= 0) {\n // Find all buffers that should be flushed\n const keysToFlush = new Set<string>();\n\n for (const [bufferedKey] of buffers) {\n if (bufferedKey === bufferKey) {\n keysToFlush.add(bufferedKey);\n } else {\n const { category: bufferedCategory, context: bufferedContext } =\n parseBufferKey(bufferedKey);\n const { context: triggerContext } = parseBufferKey(bufferKey);\n\n // Check context match\n let contextMatches = true;\n if (isolateByContext) {\n contextMatches = bufferedContext === triggerContext;\n }\n\n // Check category match\n let categoryMatches = false;\n if (!isolateByCategory) {\n // No category isolation, so all categories match if context matches\n categoryMatches = contextMatches;\n } else if (shouldFlushBuffer) {\n try {\n categoryMatches = shouldFlushBuffer(\n record.category,\n bufferedCategory,\n );\n } catch {\n // Ignore errors from custom matcher\n }\n } else {\n // Same category only\n categoryMatches = getCategoryKey(record.category) ===\n getCategoryKey(bufferedCategory);\n }\n\n // Both must match for the buffer to be flushed\n if (contextMatches && categoryMatches) {\n keysToFlush.add(bufferedKey);\n }\n }\n }\n\n // Flush matching buffers\n const allRecordsToFlush: LogRecord[] = [];\n for (const key of keysToFlush) {\n const metadata = buffers.get(key);\n if (metadata) {\n allRecordsToFlush.push(...metadata.buffer);\n buffers.delete(key);\n triggered.add(key);\n }\n }\n\n // Sort by timestamp to maintain chronological order\n allRecordsToFlush.sort((a, b) => a.timestamp - b.timestamp);\n\n // Flush all records\n for (const bufferedRecord of allRecordsToFlush) {\n sink(bufferedRecord);\n }\n\n // Mark trigger buffer as triggered and send trigger record\n triggered.add(bufferKey);\n sink(record);\n } else if (\n bufferLevel != null &&\n compareLogLevel(record.level, bufferLevel) > 0\n ) {\n // Record is above bufferLevel but below triggerLevel: pass through\n sink(record);\n } else {\n // Buffer the record\n let metadata = buffers.get(bufferKey);\n if (!metadata) {\n // Apply LRU eviction if adding new buffer would exceed capacity\n if (hasLru && buffers.size >= maxContexts!) {\n // Calculate how many buffers to evict to make room for the new one\n const numToEvict = buffers.size - maxContexts! + 1;\n evictLruBuffers(buffers, numToEvict);\n }\n\n metadata = {\n buffer: [],\n lastAccess: ++accessCounter,\n };\n buffers.set(bufferKey, metadata);\n } else {\n // Update last access order for LRU\n metadata.lastAccess = ++accessCounter;\n }\n\n metadata.buffer.push(record);\n\n // Enforce max buffer size per buffer\n while (metadata.buffer.length > maxBufferSize) {\n metadata.buffer.shift();\n }\n }\n };\n\n // Add disposal functionality to clean up timer\n if (cleanupTimer !== null) {\n (fingersCrossedSink as Sink & Disposable)[Symbol.dispose] = () => {\n if (cleanupTimer !== null) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n }\n };\n }\n\n return fingersCrossedSink;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,WAAWA,MAAYC,QAA0B;CAC/D,MAAM,aAAa,SAAS,OAAO;AACnC,QAAO,CAACC,WAAsB;AAC5B,MAAI,WAAW,OAAO,CAAE,MAAK,OAAO;CACrC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;AA6ED,SAAgB,cACdC,QACAC,UAA6B,CAAE,GACP;CACxB,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,UAAU,QAAQ,WAAW,IAAI;CACvC,MAAM,SAAS,OAAO,WAAW;AAEjC,MAAK,QAAQ,aAAa;EACxB,IAAI,cAAc,QAAQ,SAAS;EACnC,MAAMC,OAA+B,CAACH,WAAsB;GAC1D,MAAM,QAAQ,QAAQ,OAAO,UAAU,OAAO,CAAC;AAC/C,iBAAc,YACX,KAAK,MAAM,OAAO,MAAM,CACxB,KAAK,MAAM,OAAO,MAAM,MAAM,CAAC;EACnC;AACD,OAAK,OAAO,gBAAgB,YAAY;AACtC,SAAM;AACN,SAAM,OAAO,OAAO;EACrB;AACD,SAAO;CACR;CAGD,MAAM,oBAAoB,QAAQ,gBAAgB,OAC9C,CAAE,IACF,QAAQ;CACZ,MAAM,aAAa,kBAAkB,cAAc;CACnD,MAAM,gBAAgB,kBAAkB,iBAAiB;CAEzD,MAAMI,SAAsB,CAAE;CAC9B,IAAIC,aAAoD;CACxD,IAAI,WAAW;CACf,IAAIC,cAAoC;CACxC,MAAM,gBAAgB,aAAa;CAEnC,eAAe,QAAuB;AACpC,MAAI,OAAO,WAAW,EAAG;EAEzB,MAAM,UAAU,OAAO,OAAO,EAAE;AAChC,OAAK,MAAM,UAAU,QACnB,KAAI;GACF,MAAM,QAAQ,QAAQ,OAAO,UAAU,OAAO,CAAC;AAC/C,SAAM,OAAO;AACb,SAAM,OAAO,MAAM,MAAM;EAC1B,QAAO,CAEP;CAEJ;CAED,SAAS,gBAAsB;AAC7B,MAAI,YAAa;AAEjB,gBAAc,OAAO,CAAC,QAAQ,MAAM;AAClC,iBAAc;EACf,EAAC;CACH;CAED,SAAS,kBAAwB;AAC/B,MAAI,eAAe,QAAQ,SAAU;AAErC,eAAa,YAAY,MAAM;AAC7B,kBAAe;EAChB,GAAE,cAAc;CAClB;CAED,MAAMC,kBAA0C,CAACP,WAAsB;AACrE,MAAI,SAAU;AAGd,MAAI,OAAO,UAAU,cACnB,QAAO,OAAO;AAGhB,SAAO,KAAK,OAAO;AAEnB,MAAI,OAAO,UAAU,WACnB,gBAAe;WACN,eAAe,KACxB,kBAAiB;CAEpB;AAED,iBAAgB,OAAO,gBAAgB,YAAY;AACjD,aAAW;AACX,MAAI,eAAe,MAAM;AACvB,iBAAc,WAAW;AACzB,gBAAa;EACd;AACD,QAAM,OAAO;AACb,MAAI;AACF,SAAM,OAAO,OAAO;EACrB,QAAO,CAEP;CACF;AAED,QAAO;AACR;;;;;;;;AAgFD,SAAgB,eACdQ,UAA8B,CAAE,GACJ;CAC5B,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAMC,WAA4C;EAChD,OAAO;EACP,OAAO;EACP,MAAM;EACN,SAAS;EACT,OAAO;EACP,OAAO;EACP,GAAI,QAAQ,YAAY,CAAE;CAC3B;CACD,MAAM,UAAU,QAAQ,WAAW,WAAW;CAE9C,MAAM,WAAW,CAACT,WAAsB;EACtC,MAAM,OAAO,UAAU,OAAO;EAC9B,MAAM,SAAS,SAAS,OAAO;AAC/B,MAAI,kBACF,OAAM,IAAI,WAAW,qBAAqB,OAAO,MAAM;AAEzD,aAAW,SAAS,UAAU;GAC5B,MAAM,MAAM,KAAK,QAAQ,UAAU,GAAG;AACtC,WAAQ,QAAQ,IAAI;EACrB,MACC,SAAQ,QAAQ,GAAG,KAAK;CAE3B;AAED,MAAK,QAAQ,YACX,QAAO;CAIT,MAAM,oBAAoB,QAAQ,gBAAgB,OAC9C,CAAE,IACF,QAAQ;CACZ,MAAM,aAAa,kBAAkB,cAAc;CACnD,MAAM,gBAAgB,kBAAkB,iBAAiB;CAEzD,MAAMI,SAAsB,CAAE;CAC9B,IAAIC,aAAoD;CACxD,IAAI,WAAW;CACf,IAAI,iBAAiB;CACrB,MAAM,gBAAgB,aAAa;CAEnC,SAAS,QAAc;AACrB,MAAI,OAAO,WAAW,EAAG;EAEzB,MAAM,UAAU,OAAO,OAAO,EAAE;AAChC,OAAK,MAAM,UAAU,QACnB,KAAI;AACF,YAAS,OAAO;EACjB,QAAO,CAEP;CAEJ;CAED,SAAS,gBAAsB;AAC7B,MAAI,eAAgB;AAEpB,mBAAiB;AACjB,aAAW,MAAM;AACf,oBAAiB;AACjB,UAAO;EACR,GAAE,EAAE;CACN;CAED,SAAS,kBAAwB;AAC/B,MAAI,eAAe,QAAQ,SAAU;AAErC,eAAa,YAAY,MAAM;AAC7B,UAAO;EACR,GAAE,cAAc;CAClB;CAED,MAAMK,kBAAqC,CAACV,WAAsB;AAChE,MAAI,SAAU;AAGd,MAAI,OAAO,UAAU,cACnB,QAAO,OAAO;AAGhB,SAAO,KAAK,OAAO;AAEnB,MAAI,OAAO,UAAU,WACnB,gBAAe;WACN,eAAe,KACxB,kBAAiB;CAEpB;AAED,iBAAgB,OAAO,WAAW,MAAM;AACtC,aAAW;AACX,MAAI,eAAe,MAAM;AACvB,iBAAc,WAAW;AACzB,gBAAa;EACd;AACD,SAAO;CACR;AAED,QAAO;AACR;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,cAAcW,WAA8C;CAC1E,IAAI,cAAc,QAAQ,SAAS;CACnC,MAAMR,OAA+B,CAACH,WAAsB;AAC1D,gBAAc,YACX,KAAK,MAAM,UAAU,OAAO,CAAC,CAC7B,MAAM,MAAM,CAEZ,EAAC;CACL;AACD,MAAK,OAAO,gBAAgB,YAAY;AACtC,QAAM;CACP;AACD,QAAO;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiND,SAAgB,eACdF,MACAc,UAAiC,CAAE,GACP;CAC5B,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,cAAc,QAAQ;CAC5B,MAAM,gBAAgB,KAAK,IAAI,GAAG,QAAQ,iBAAiB,IAAK;CAChE,MAAM,oBAAoB,QAAQ;CAClC,MAAM,mBAAmB,QAAQ;CAGjC,MAAM,cAAc,kBAAkB;CACtC,MAAM,oBAAoB,kBAAkB,qBAAqB;CACjE,MAAM,cAAc,kBAAkB;CACtC,MAAM,SAAS,eAAe,QAAQ,cAAc;CACpD,MAAM,SAAS,eAAe,QAAQ,cAAc;AAGpD,KAAI;AACF,kBAAgB,SAAS,aAAa;CACvC,SAAQ,OAAO;AACd,QAAM,IAAI,WACP,wBAAwB,KAAK,UAAU,aAAa,CAAC,IACpD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;CAEJ;AAGD,KAAI,eAAe,MAAM;AACvB,MAAI;AACF,mBAAgB,SAAS,YAAY;EACtC,SAAQ,OAAO;AACd,SAAM,IAAI,WACP,uBAAuB,KAAK,UAAU,YAAY,CAAC,IAClD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;EAEJ;AAGD,MAAI,gBAAgB,aAAa,aAAa,IAAI,EAChD,OAAM,IAAI,YACP,eAAe,KAAK,UAAU,YAAY,CAAC,qCACzB,KAAK,UAAU,aAAa,CAAC;CAGrD;CAGD,SAAS,aACPC,QACAC,OACS;AACT,MAAI,OAAO,WAAW,KAAK,MAAM,WAAW,EAAG,QAAO;AACtD,MAAI,OAAO,SAAS,MAAM,OAAQ,QAAO;AACzC,SAAO,OAAO,MAAM,CAAC,GAAG,MAAM,MAAM,MAAM,GAAG;CAC9C;CAED,SAAS,WACPA,OACAD,QACS;AACT,MAAI,MAAM,WAAW,KAAK,OAAO,WAAW,EAAG,QAAO;AACtD,MAAI,MAAM,SAAS,OAAO,OAAQ,QAAO;AACzC,SAAO,OAAO,MAAM,CAAC,GAAG,MAAM,MAAM,MAAM,GAAG;CAC9C;CAGD,IAAIE,oBAKO;AAEX,KAAI,kBACF,YAAW,sBAAsB,WAC/B,qBAAoB;KAEpB,SAAQ,mBAAR;EACE,KAAK;AACH,uBAAoB,CAAC,SAAS,aAC5B,aAAa,SAAS,SAAS;AACjC;EACF,KAAK;AACH,uBAAoB,CAAC,SAAS,aAC5B,WAAW,SAAS,SAAS;AAC/B;EACF,KAAK;AACH,uBAAoB,CAAC,SAAS,aAC5B,aAAa,SAAS,SAAS,IAAI,WAAW,SAAS,SAAS;AAClE;CACH;CAKL,SAAS,eAAeC,UAAqC;AAC3D,SAAO,KAAK,UAAU,SAAS;CAChC;CAED,SAAS,iBAAiBC,KAAuB;AAC/C,SAAO,KAAK,MAAM,IAAI;CACvB;CAGD,SAAS,cAAcC,YAA6C;AAClE,OAAK,oBAAoB,iBAAiB,KAAK,WAAW,EACxD,QAAO;EAET,MAAMC,gBAAyC,CAAE;AACjD,OAAK,MAAM,OAAO,iBAAiB,KACjC,KAAI,OAAO,WACT,eAAc,OAAO,WAAW;AAGpC,SAAO,KAAK,UAAU,cAAc;CACrC;CAGD,SAAS,aACPH,UACAE,YACQ;EACR,MAAM,cAAc,eAAe,SAAS;AAC5C,OAAK,iBACH,QAAO;EAET,MAAM,aAAa,cAAc,WAAW;AAC5C,UAAQ,EAAE,YAAY,GAAG,WAAW;CACrC;CAGD,SAAS,eAAeD,KAGtB;AACA,OAAK,iBACH,QAAO;GAAE,UAAU,iBAAiB,IAAI;GAAE,SAAS;EAAI;EAKzD,MAAM,iBAAiB,IAAI,QAAQ,KAAK;AACxC,MAAI,mBAAmB,GAErB,QAAO;GAAE,UAAU,iBAAiB,IAAI;GAAE,SAAS;EAAI;EAEzD,MAAM,eAAe,IAAI,UAAU,GAAG,iBAAiB,EAAE;EACzD,MAAM,cAAc,IAAI,UAAU,iBAAiB,EAAE;AACrD,SAAO;GAAE,UAAU,iBAAiB,aAAa;GAAE,SAAS;EAAa;CAC1E;CAGD,SAAS,sBAAsBG,SAA4C;AACzE,OAAK,OAAQ;EAEb,MAAM,MAAM,KAAK,KAAK;EACtB,MAAMC,cAAwB,CAAE;AAEhC,OAAK,MAAM,CAAC,KAAK,SAAS,IAAI,SAAS;AACrC,OAAI,SAAS,OAAO,WAAW,EAAG;GAGlC,MAAM,sBACJ,SAAS,OAAO,SAAS,OAAO,SAAS,GAAG;AAC9C,OAAI,MAAM,sBAAsB,YAC9B,aAAY,KAAK,IAAI;EAExB;AAGD,OAAK,MAAM,OAAO,YAChB,SAAQ,OAAO,IAAI;CAEtB;CAGD,SAAS,gBACPD,SACAE,YACM;AACN,OAAK,OAAQ;EAGb,MAAM,UAAU,cAAc,KAAK,IAAI,GAAG,QAAQ,OAAO,YAAa;AACtE,MAAI,WAAW,EAAG;EAGlB,MAAM,gBAAgB,MAAM,KAAK,QAAQ,SAAS,CAAC,CAChD,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW;AAGtD,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;GAChC,MAAM,CAAC,IAAI,GAAG,cAAc;AAC5B,WAAQ,OAAO,IAAI;EACpB;CACF;AAGD,MAAK,sBAAsB,kBAAkB;EAE3C,MAAMlB,SAAsB,CAAE;EAC9B,IAAI,YAAY;AAEhB,SAAO,CAACJ,WAAsB;AAC5B,OAAI,WAAW;AAEb,SAAK,OAAO;AACZ;GACD;AAGD,OAAI,gBAAgB,OAAO,OAAO,aAAa,IAAI,GAAG;AACpD,gBAAY;AAGZ,SAAK,MAAM,kBAAkB,OAC3B,MAAK,eAAe;AAEtB,WAAO,SAAS;AAGhB,SAAK,OAAO;GACb,WACC,eAAe,QACf,gBAAgB,OAAO,OAAO,YAAY,GAAG,EAG7C,MAAK,OAAO;QACP;AAEL,WAAO,KAAK,OAAO;AAGnB,WAAO,OAAO,SAAS,cACrB,QAAO,OAAO;GAEjB;EACF;CACF,OAAM;EAEL,MAAM,0BAAU,IAAI;EACpB,MAAM,4BAAY,IAAI;EACtB,IAAI,gBAAgB;EAGpB,IAAIuB,eAAsD;AAC1D,MAAI,OACF,gBAAe,YAAY,MAAM;AAC/B,yBAAsB,QAAQ;EAC/B,GAAE,kBAAkB;EAGvB,MAAM,qBAAqB,CAACvB,WAAsB;GAChD,MAAM,YAAY,aAAa,OAAO,UAAU,OAAO,WAAW;AAGlE,OAAI,UAAU,IAAI,UAAU,EAAE;AAC5B,SAAK,OAAO;AACZ;GACD;AAGD,OAAI,gBAAgB,OAAO,OAAO,aAAa,IAAI,GAAG;IAEpD,MAAM,8BAAc,IAAI;AAExB,SAAK,MAAM,CAAC,YAAY,IAAI,QAC1B,KAAI,gBAAgB,UAClB,aAAY,IAAI,YAAY;SACvB;KACL,MAAM,EAAE,UAAU,kBAAkB,SAAS,iBAAiB,GAC5D,eAAe,YAAY;KAC7B,MAAM,EAAE,SAAS,gBAAgB,GAAG,eAAe,UAAU;KAG7D,IAAI,iBAAiB;AACrB,SAAI,iBACF,kBAAiB,oBAAoB;KAIvC,IAAI,kBAAkB;AACtB,UAAK,kBAEH,mBAAkB;cACT,kBACT,KAAI;AACF,wBAAkB,kBAChB,OAAO,UACP,iBACD;KACF,QAAO,CAEP;SAGD,mBAAkB,eAAe,OAAO,SAAS,KAC/C,eAAe,iBAAiB;AAIpC,SAAI,kBAAkB,gBACpB,aAAY,IAAI,YAAY;IAE/B;IAIH,MAAMwB,oBAAiC,CAAE;AACzC,SAAK,MAAM,OAAO,aAAa;KAC7B,MAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,SAAI,UAAU;AACZ,wBAAkB,KAAK,GAAG,SAAS,OAAO;AAC1C,cAAQ,OAAO,IAAI;AACnB,gBAAU,IAAI,IAAI;KACnB;IACF;AAGD,sBAAkB,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;AAG3D,SAAK,MAAM,kBAAkB,kBAC3B,MAAK,eAAe;AAItB,cAAU,IAAI,UAAU;AACxB,SAAK,OAAO;GACb,WACC,eAAe,QACf,gBAAgB,OAAO,OAAO,YAAY,GAAG,EAG7C,MAAK,OAAO;QACP;IAEL,IAAI,WAAW,QAAQ,IAAI,UAAU;AACrC,SAAK,UAAU;AAEb,SAAI,UAAU,QAAQ,QAAQ,aAAc;MAE1C,MAAM,aAAa,QAAQ,OAAO,cAAe;AACjD,sBAAgB,SAAS,WAAW;KACrC;AAED,gBAAW;MACT,QAAQ,CAAE;MACV,YAAY,EAAE;KACf;AACD,aAAQ,IAAI,WAAW,SAAS;IACjC,MAEC,UAAS,aAAa,EAAE;AAG1B,aAAS,OAAO,KAAK,OAAO;AAG5B,WAAO,SAAS,OAAO,SAAS,cAC9B,UAAS,OAAO,OAAO;GAE1B;EACF;AAGD,MAAI,iBAAiB,KACnB,CAAC,mBAAyC,OAAO,WAAW,MAAM;AAChE,OAAI,iBAAiB,MAAM;AACzB,kBAAc,aAAa;AAC3B,mBAAe;GAChB;EACF;AAGH,SAAO;CACR;AACF"}
|
package/package.json
CHANGED