@logtape/logtape 0.3.0-dev.27 → 0.3.0-dev.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -293,13 +293,17 @@ await configure({
293
293
  See also [`getFileSink()`] function and [`FileSinkOptions`] interface
294
294
  in the API reference for more details.
295
295
 
296
+ > [!NOTE]
297
+ > On Deno, you need to have the `--allow-write` flag and the `--unstable-fs`
298
+ > flag to use the file sink.
299
+
296
300
  [`getFileSink()`]: https://jsr.io/@logtape/logtape/doc/~/getFileSink
297
301
  [`FileSinkOptions`]: https://jsr.io/@logtape/logtape/doc/~/FileSinkOptions
298
302
 
299
303
  ### Rotating file sink
300
304
 
301
305
  > [!NOTE]
302
- > File sink is unavailable in the browser environment.
306
+ > Rotating file sink is unavailable in the browser environment.
303
307
 
304
308
  A rotating file sink is a file sink that rotates log files. It creates a new
305
309
  log file when the current log file reaches a certain size. Here's an example
@@ -324,6 +328,10 @@ Rotated log files are named with a suffix like *.1*, *.2*, *.3*, and so on.
324
328
  For more details, see [`getRotatingFileSink()`] function and
325
329
  [`RotatingFileSinkOptions`] interface in the API reference.
326
330
 
331
+ > [!NOTE]
332
+ > On Deno, you need to have the `--allow-write` flag and the `--unstable-fs`
333
+ > flag to use the rotating file sink.
334
+
327
335
  [`getRotatingFileSink()`]: https://jsr.io/@logtape/logtape/doc/~/getRotatingFileSink
328
336
  [`RotatingFileSinkOptions`]: https://jsr.io/@logtape/logtape/doc/~/RotatingFileSinkOptions
329
337
 
@@ -343,7 +351,9 @@ Here's an example of a text formatter that writes log messages in a JSON format:
343
351
  await configure({
344
352
  sinks: {
345
353
  stream: getStreamSink(Deno.stderr.writable, {
346
- formatter: JSON.stringify,
354
+ formatter(log) {
355
+ return JSON.stringify(log) + "\n",
356
+ }
347
357
  }),
348
358
  },
349
359
  // Omitted for brevity
@@ -408,6 +418,91 @@ export default {
408
418
  [`ctx.waitUntil()`]: https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil
409
419
 
410
420
 
421
+ Filters
422
+ -------
423
+
424
+ A filter is a function that filters log messages. A filter takes a log record
425
+ and returns a boolean value. If the filter returns `true`, the log record is
426
+ passed to the sinks; otherwise, the log record is discarded. Its signature is:
427
+
428
+ ~~~~ typescript
429
+ export type Filter = (record: LogRecord) => boolean;
430
+ ~~~~
431
+
432
+ For example, the following filter discards log messages whose property `elapsed`
433
+ is less than 100 milliseconds:
434
+
435
+ ~~~~ typescript
436
+ import { configure, type LogRecord } from "@logtape/logtape";
437
+
438
+ await configure({
439
+ // Omitted for brevity
440
+ filters: {
441
+ tooSlow(record: LogRecord) {
442
+ return "elapsed" in record.properties && record.properties.elapsed >= 100;
443
+ },
444
+ },
445
+ loggers: [
446
+ {
447
+ category: ["my-app", "database"],
448
+ level: "debug",
449
+ sinks: ["console"],
450
+ filters: ["tooSlow"],
451
+ }
452
+ ]
453
+ });
454
+ ~~~~
455
+
456
+ ### Level filter
457
+
458
+ LogTape provides a built-in level filter. You can use the level filter to
459
+ filter log messages by their log levels. The level filter factory takes
460
+ a [`LogLevel`] string and returns a level filter. For example, the following
461
+ level filter discards log messages whose log level is less than `info`:
462
+
463
+ ~~~~ typescript
464
+ import { getLevelFilter } from "@logtape/logtape";
465
+
466
+ await configure({
467
+ filters: {
468
+ infoOrHigher: getLevelFilter("info");
469
+ },
470
+ // Omitted for brevity
471
+ });
472
+ ~~~~
473
+
474
+ [`LogLevel`]: https://jsr.io/@logtape/logtape/doc/~/LogLevel
475
+
476
+ ### Sink filter
477
+
478
+ A sink filter is a filter that is applied to a specific sink. You can add a
479
+ sink filter to a sink by decorating the sink with [`withFilter()`]:
480
+
481
+ ~~~~ typescript
482
+ import { getConsoleSink, withFilter } from "@logtape/logtape";
483
+
484
+ await configure({
485
+ sinks: {
486
+ filteredConsole: withFilter(
487
+ getConsoleSink(),
488
+ log => "elapsed" in log.properties && log.properties.elapsed >= 100,
489
+ ),
490
+ },
491
+ // Omitted for brevity
492
+ });
493
+ ~~~~
494
+
495
+ The `filteredConsoleSink` only logs messages whose property `elapsed` is greater
496
+ than or equal to 100 milliseconds to the console.
497
+
498
+ > [!TIP]
499
+ > The `withFilter()` function can take a [`LogLevel`] string as the second
500
+ > argument. In this case, the log messages whose log level is less than
501
+ > the specified log level are discarded.
502
+
503
+ [`withFilter()`]: https://jsr.io/@logtape/logtape/doc/~/withFilter
504
+
505
+
411
506
  Testing
412
507
  -------
413
508
 
package/esm/mod.js CHANGED
@@ -4,5 +4,5 @@ export { getLevelFilter, toFilter, } from "./filter.js";
4
4
  export { defaultConsoleFormatter, defaultTextFormatter, } from "./formatter.js";
5
5
  export { isLogLevel, parseLogLevel } from "./level.js";
6
6
  export { getLogger } from "./logger.js";
7
- export { getConsoleSink, getStreamSink, } from "./sink.js";
7
+ export { getConsoleSink, getStreamSink, withFilter, } from "./sink.js";
8
8
  // cSpell: ignore filesink
package/esm/sink.js CHANGED
@@ -1,4 +1,26 @@
1
+ import { toFilter } from "./filter.js";
1
2
  import { defaultConsoleFormatter, defaultTextFormatter, } from "./formatter.js";
3
+ /**
4
+ * Turns a sink into a filtered sink. The returned sink only logs records that
5
+ * pass the filter.
6
+ *
7
+ * @example Filter a console sink to only log records with the info level
8
+ * ```typescript
9
+ * const sink = withFilter(getConsoleSink(), "info");
10
+ * ```
11
+ *
12
+ * @param sink A sink to be filtered.
13
+ * @param filter A filter to apply to the sink. It can be either a filter
14
+ * function or a {@link LogLevel} string.
15
+ * @returns A sink that only logs records that pass the filter.
16
+ */
17
+ export function withFilter(sink, filter) {
18
+ const filterFunc = toFilter(filter);
19
+ return (record) => {
20
+ if (filterFunc(record))
21
+ sink(record);
22
+ };
23
+ }
2
24
  /**
3
25
  * A factory that returns a sink that writes to a {@link WritableStream}.
4
26
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/logtape",
3
- "version": "0.3.0-dev.27+42b10385",
3
+ "version": "0.3.0-dev.29+5a800eb8",
4
4
  "description": "Simple logging library with zero dependencies for Deno/Node.js/Bun/browsers",
5
5
  "keywords": [
6
6
  "logging",
package/script/mod.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getStreamSink = exports.getConsoleSink = exports.getLogger = exports.parseLogLevel = exports.isLogLevel = exports.defaultTextFormatter = exports.defaultConsoleFormatter = exports.toFilter = exports.getLevelFilter = exports.getRotatingFileSink = exports.getFileSink = exports.reset = exports.getConfig = exports.dispose = exports.configure = exports.ConfigError = void 0;
3
+ exports.withFilter = exports.getStreamSink = exports.getConsoleSink = exports.getLogger = exports.parseLogLevel = exports.isLogLevel = exports.defaultTextFormatter = exports.defaultConsoleFormatter = exports.toFilter = exports.getLevelFilter = exports.getRotatingFileSink = exports.getFileSink = exports.reset = exports.getConfig = exports.dispose = exports.configure = exports.ConfigError = void 0;
4
4
  var config_js_1 = require("./config.js");
5
5
  Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return config_js_1.ConfigError; } });
6
6
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return config_js_1.configure; } });
@@ -24,4 +24,5 @@ Object.defineProperty(exports, "getLogger", { enumerable: true, get: function ()
24
24
  var sink_js_1 = require("./sink.js");
25
25
  Object.defineProperty(exports, "getConsoleSink", { enumerable: true, get: function () { return sink_js_1.getConsoleSink; } });
26
26
  Object.defineProperty(exports, "getStreamSink", { enumerable: true, get: function () { return sink_js_1.getStreamSink; } });
27
+ Object.defineProperty(exports, "withFilter", { enumerable: true, get: function () { return sink_js_1.withFilter; } });
27
28
  // cSpell: ignore filesink
package/script/sink.js CHANGED
@@ -1,7 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRotatingFileSink = exports.getFileSink = exports.getConsoleSink = exports.getStreamSink = void 0;
3
+ exports.getRotatingFileSink = exports.getFileSink = exports.getConsoleSink = exports.getStreamSink = exports.withFilter = void 0;
4
+ const filter_js_1 = require("./filter.js");
4
5
  const formatter_js_1 = require("./formatter.js");
6
+ /**
7
+ * Turns a sink into a filtered sink. The returned sink only logs records that
8
+ * pass the filter.
9
+ *
10
+ * @example Filter a console sink to only log records with the info level
11
+ * ```typescript
12
+ * const sink = withFilter(getConsoleSink(), "info");
13
+ * ```
14
+ *
15
+ * @param sink A sink to be filtered.
16
+ * @param filter A filter to apply to the sink. It can be either a filter
17
+ * function or a {@link LogLevel} string.
18
+ * @returns A sink that only logs records that pass the filter.
19
+ */
20
+ function withFilter(sink, filter) {
21
+ const filterFunc = (0, filter_js_1.toFilter)(filter);
22
+ return (record) => {
23
+ if (filterFunc(record))
24
+ sink(record);
25
+ };
26
+ }
27
+ exports.withFilter = withFilter;
5
28
  /**
6
29
  * A factory that returns a sink that writes to a {@link WritableStream}.
7
30
  *
package/types/mod.d.ts CHANGED
@@ -5,5 +5,5 @@ export { type ConsoleFormatter, defaultConsoleFormatter, defaultTextFormatter, t
5
5
  export { isLogLevel, type LogLevel, parseLogLevel } from "./level.js";
6
6
  export { getLogger, type Logger } from "./logger.js";
7
7
  export type { LogRecord } from "./record.js";
8
- export { type ConsoleSinkOptions, type FileSinkOptions, getConsoleSink, getStreamSink, type RotatingFileSinkOptions, type Sink, type StreamSinkOptions, } from "./sink.js";
8
+ export { type ConsoleSinkOptions, type FileSinkOptions, getConsoleSink, getStreamSink, type RotatingFileSinkOptions, type Sink, type StreamSinkOptions, withFilter, } from "./sink.js";
9
9
  //# sourceMappingURL=mod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,gBAAgB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,cAAc,EACd,aAAa,EACb,KAAK,uBAAuB,EAC5B,KAAK,IAAI,EACT,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,gBAAgB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,cAAc,EACd,aAAa,EACb,KAAK,uBAAuB,EAC5B,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,UAAU,GACX,MAAM,WAAW,CAAC"}
package/types/sink.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  import * as dntShim from "./_dnt.shims.js";
5
+ import { type FilterLike } from "./filter.js";
5
6
  import { type ConsoleFormatter, type TextFormatter } from "./formatter.js";
6
7
  import type { LogRecord } from "./record.js";
7
8
  /**
@@ -14,6 +15,21 @@ import type { LogRecord } from "./record.js";
14
15
  * @param record The log record to sink.
15
16
  */
16
17
  export type Sink = (record: LogRecord) => void;
18
+ /**
19
+ * Turns a sink into a filtered sink. The returned sink only logs records that
20
+ * pass the filter.
21
+ *
22
+ * @example Filter a console sink to only log records with the info level
23
+ * ```typescript
24
+ * const sink = withFilter(getConsoleSink(), "info");
25
+ * ```
26
+ *
27
+ * @param sink A sink to be filtered.
28
+ * @param filter A filter to apply to the sink. It can be either a filter
29
+ * function or a {@link LogLevel} string.
30
+ * @returns A sink that only logs records that pass the filter.
31
+ */
32
+ export declare function withFilter(sink: Sink, filter: FilterLike): Sink;
17
33
  /**
18
34
  * Options for the {@link getStreamSink} function.
19
35
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,KAAK,gBAAgB,EAGrB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,CAAC,cAAc,EAC9B,OAAO,GAAE,iBAAsB,GAC9B,IAAI,GAAG,eAAe,CAgBxB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAE7B;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,IAAI,CAYrE;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAE9C;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,GAC/C,IAAI,GAAG,UAAU,CAUnB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK,CAAE,SAAQ,cAAc,CAAC,KAAK,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzC;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAAC,KAAK,CAAC,GAC/D,IAAI,GAAG,UAAU,CAkCnB"}
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,KAAK,gBAAgB,EAGrB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAK/D;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,CAAC,cAAc,EAC9B,OAAO,GAAE,iBAAsB,GAC9B,IAAI,GAAG,eAAe,CAgBxB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAE7B;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,IAAI,CAYrE;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAE9C;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,GAC/C,IAAI,GAAG,UAAU,CAUnB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK,CAAE,SAAQ,cAAc,CAAC,KAAK,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzC;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAAC,KAAK,CAAC,GAC/D,IAAI,GAAG,UAAU,CAkCnB"}