@logtape/file 2.2.0-dev.679 → 2.2.0-dev.683
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/dist/filesink.base.d.cts +74 -0
- package/dist/dist/filesink.base.d.cts.map +1 -0
- package/dist/dist/filesink.node.d.cts +65 -0
- package/dist/dist/filesink.node.d.cts.map +1 -0
- package/dist/dist/timefilesink.d.cts +41 -0
- package/dist/dist/timefilesink.d.cts.map +1 -0
- package/dist/filesink.base.cjs +104 -20
- package/dist/filesink.base.d.cts +10 -0
- package/dist/filesink.base.d.cts.map +1 -1
- package/dist/filesink.base.d.ts +10 -0
- package/dist/filesink.base.d.ts.map +1 -1
- package/dist/filesink.base.js +105 -21
- package/dist/filesink.base.js.map +1 -1
- package/dist/filesink.deno.cjs +2 -1
- package/dist/filesink.deno.d.cts.map +1 -1
- package/dist/filesink.deno.d.ts.map +1 -1
- package/dist/filesink.deno.js +2 -1
- package/dist/filesink.deno.js.map +1 -1
- package/dist/filesink.node.cjs +2 -1
- package/dist/filesink.node.d.cts.map +1 -1
- package/dist/filesink.node.d.ts.map +1 -1
- package/dist/filesink.node.js +2 -1
- package/dist/filesink.node.js.map +1 -1
- package/dist/mod.d.cts +1 -1
- package/dist/timefilesink.cjs +17 -4
- package/dist/timefilesink.js +17 -4
- package/dist/timefilesink.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { StreamSinkOptions } from "@logtape/logtape";
|
|
2
|
+
|
|
3
|
+
//#region dist/filesink.base.d.ts
|
|
4
|
+
|
|
5
|
+
//#region src/filesink.base.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Options for the {@link getBaseFileSink} function.
|
|
8
|
+
*/
|
|
9
|
+
interface FileSinkOptions extends StreamSinkOptions {
|
|
10
|
+
/**
|
|
11
|
+
* If `true`, the file is not opened until the first write. Defaults to `false`.
|
|
12
|
+
*/
|
|
13
|
+
lazy?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The size of the buffer to use when writing to the file. If not specified,
|
|
16
|
+
* a default buffer size will be used. If it is less or equal to 0,
|
|
17
|
+
* the file will be written directly without buffering.
|
|
18
|
+
* @default 8192
|
|
19
|
+
* @since 0.12.0
|
|
20
|
+
*/
|
|
21
|
+
bufferSize?: number;
|
|
22
|
+
/**
|
|
23
|
+
* The maximum time interval in milliseconds between flushes. If this time
|
|
24
|
+
* passes since the last flush, the buffer will be flushed regardless of size.
|
|
25
|
+
* This helps prevent log loss during unexpected process termination.
|
|
26
|
+
* @default 5000
|
|
27
|
+
* @since 0.12.0
|
|
28
|
+
*/
|
|
29
|
+
flushInterval?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Enable non-blocking mode with background flushing.
|
|
32
|
+
* When enabled, flush operations are performed asynchronously to prevent
|
|
33
|
+
* blocking the main thread during file I/O operations.
|
|
34
|
+
*
|
|
35
|
+
* @default `false`
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
*/
|
|
38
|
+
nonBlocking?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A platform-specific file sink driver.
|
|
42
|
+
* @template TFile The type of the file descriptor.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get a platform-independent file sink.
|
|
47
|
+
*
|
|
48
|
+
* @template TFile The type of the file descriptor.
|
|
49
|
+
* @param path A path to the file to write to.
|
|
50
|
+
* @param options The options for the sink and the file driver.
|
|
51
|
+
* @returns A sink that writes to the file. The sink is also a disposable
|
|
52
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
53
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Options for the {@link getBaseRotatingFileSink} function.
|
|
58
|
+
*/
|
|
59
|
+
interface RotatingFileSinkOptions extends Omit<FileSinkOptions, "lazy"> {
|
|
60
|
+
/**
|
|
61
|
+
* The maximum bytes of the file before it is rotated. 1 MiB by default.
|
|
62
|
+
*/
|
|
63
|
+
maxSize?: number;
|
|
64
|
+
/**
|
|
65
|
+
* The maximum number of files to keep. 5 by default.
|
|
66
|
+
*/
|
|
67
|
+
maxFiles?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A platform-specific rotating file sink driver.
|
|
71
|
+
*/
|
|
72
|
+
//#endregion
|
|
73
|
+
export { FileSinkOptions, RotatingFileSinkOptions };
|
|
74
|
+
//# sourceMappingURL=filesink.base.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesink.base.d.cts","names":["Sink","StreamSinkOptions","FileSinkOptions","FileSinkDriver","TFile","Uint8Array","AsyncFileSinkDriver","Promise","RotatingFileSinkOptions","Omit","RotatingFileSinkDriver","AsyncRotatingFileSinkDriver"],"sources":["../filesink.base.d.ts"],"sourcesContent":["import { Sink, StreamSinkOptions } from \"@logtape/logtape\";\n\n//#region src/filesink.base.d.ts\n\n/**\n * Options for the {@link getBaseFileSink} function.\n */\ninterface FileSinkOptions extends StreamSinkOptions {\n /**\n * If `true`, the file is not opened until the first write. Defaults to `false`.\n */\n lazy?: boolean;\n /**\n * The size of the buffer to use when writing to the file. If not specified,\n * a default buffer size will be used. If it is less or equal to 0,\n * the file will be written directly without buffering.\n * @default 8192\n * @since 0.12.0\n */\n bufferSize?: number;\n /**\n * The maximum time interval in milliseconds between flushes. If this time\n * passes since the last flush, the buffer will be flushed regardless of size.\n * This helps prevent log loss during unexpected process termination.\n * @default 5000\n * @since 0.12.0\n */\n flushInterval?: number;\n /**\n * Enable non-blocking mode with background flushing.\n * When enabled, flush operations are performed asynchronously to prevent\n * blocking the main thread during file I/O operations.\n *\n * @default `false`\n * @since 1.0.0\n */\n nonBlocking?: boolean;\n}\n/**\n * A platform-specific file sink driver.\n * @template TFile The type of the file descriptor.\n */\ninterface FileSinkDriver<TFile> {\n /**\n * Open a file for appending and return a file descriptor.\n * @param path A path to the file to open.\n */\n openSync(path: string): TFile;\n /**\n * Write a chunk of data to the file.\n * @param fd The file descriptor.\n * @param chunk The data to write.\n */\n writeSync(fd: TFile, chunk: Uint8Array): void;\n /**\n * Write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeManySync?(fd: TFile, chunks: Uint8Array[]): void;\n /**\n * Flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flushSync(fd: TFile): void;\n /**\n * Close the file.\n * @param fd The file descriptor.\n */\n closeSync(fd: TFile): void;\n}\n/**\n * A platform-specific async file sink driver.\n * @template TFile The type of the file descriptor.\n * @since 1.0.0\n */\ninterface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Asynchronously write multiple chunks of data to the file in a single operation.\n * This is optional - if not implemented, falls back to multiple writeSync calls.\n * @param fd The file descriptor.\n * @param chunks Array of data chunks to write.\n */\n writeMany?(fd: TFile, chunks: Uint8Array[]): Promise<void>;\n /**\n * Asynchronously flush the file to ensure that all data is written to the disk.\n * @param fd The file descriptor.\n */\n flush(fd: TFile): Promise<void>;\n /**\n * Asynchronously close the file.\n * @param fd The file descriptor.\n */\n close(fd: TFile): Promise<void>;\n}\n/**\n * Get a platform-independent file sink.\n *\n * @template TFile The type of the file descriptor.\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\n\n/**\n * Options for the {@link getBaseRotatingFileSink} function.\n */\ninterface RotatingFileSinkOptions extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The maximum bytes of the file before it is rotated. 1 MiB by default.\n */\n maxSize?: number;\n /**\n * The maximum number of files to keep. 5 by default.\n */\n maxFiles?: number;\n}\n/**\n * A platform-specific rotating file sink driver.\n */\ninterface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): {\n size: number;\n };\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync?(path: string): void;\n}\n/**\n * A platform-specific async rotating file sink driver.\n * @since 1.0.0\n */\ninterface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {\n /**\n * Get the size of the file.\n * @param path A path to the file.\n * @returns The `size` of the file in bytes, in an object.\n */\n statSync(path: string): {\n size: number;\n };\n /**\n * Rename a file.\n * @param oldPath A path to the file to rename.\n * @param newPath A path to be renamed to.\n */\n renameSync(oldPath: string, newPath: string): void;\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync?(path: string): void;\n}\n/**\n * Get a platform-independent rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\n//#endregion\nexport { AsyncFileSinkDriver, AsyncRotatingFileSinkDriver, FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };\n//# sourceMappingURL=filesink.base.d.ts.map"],"mappings":";;;;;AA6E2D;;;UAtEjDE,eAAAA,SAAwBD,iBAuGQQ,CAAAA;EAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAApCD,uBAAAA,SAAgCC,KAAKP"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FileSinkOptions, RotatingFileSinkOptions } from "./filesink.base.cjs";
|
|
2
|
+
import { TimeRotatingFileSinkOptions } from "./timefilesink.cjs";
|
|
3
|
+
import { Sink } from "@logtape/logtape";
|
|
4
|
+
|
|
5
|
+
//#region dist/filesink.node.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get a file sink.
|
|
9
|
+
*
|
|
10
|
+
* Note that this function is unavailable in the browser.
|
|
11
|
+
*
|
|
12
|
+
* @param path A path to the file to write to.
|
|
13
|
+
* @param options The options for the sink.
|
|
14
|
+
* @returns A sink that writes to the file. The sink is also a disposable
|
|
15
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
16
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
17
|
+
*/
|
|
18
|
+
declare function getFileSink(path: string, options?: FileSinkOptions): Sink & Disposable;
|
|
19
|
+
declare function getFileSink(path: string, options: FileSinkOptions & {
|
|
20
|
+
nonBlocking: true;
|
|
21
|
+
}): Sink & AsyncDisposable;
|
|
22
|
+
/**
|
|
23
|
+
* Get a rotating file sink.
|
|
24
|
+
*
|
|
25
|
+
* This sink writes log records to a file, and rotates the file when it reaches
|
|
26
|
+
* the `maxSize`. The rotated files are named with the original file name
|
|
27
|
+
* followed by a dot and a number, starting from 1. The number is incremented
|
|
28
|
+
* for each rotation, and the maximum number of files to keep is `maxFiles`.
|
|
29
|
+
*
|
|
30
|
+
* Note that this function is unavailable in the browser.
|
|
31
|
+
*
|
|
32
|
+
* @param path A path to the file to write to.
|
|
33
|
+
* @param options The options for the sink and the file driver.
|
|
34
|
+
* @returns A sink that writes to the file. The sink is also a disposable
|
|
35
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
36
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
37
|
+
*/
|
|
38
|
+
declare function getRotatingFileSink(path: string, options?: RotatingFileSinkOptions): Sink & Disposable;
|
|
39
|
+
declare function getRotatingFileSink(path: string, options: RotatingFileSinkOptions & {
|
|
40
|
+
nonBlocking: true;
|
|
41
|
+
}): Sink & AsyncDisposable;
|
|
42
|
+
/**
|
|
43
|
+
* Get a time-rotating file sink.
|
|
44
|
+
*
|
|
45
|
+
* This sink writes log records to a file in a directory, rotating to a new
|
|
46
|
+
* file based on time intervals. The filename is generated based on the
|
|
47
|
+
* current date/time and the configured interval.
|
|
48
|
+
*
|
|
49
|
+
* Note that this function is unavailable in the browser.
|
|
50
|
+
*
|
|
51
|
+
* @param options The options for the sink.
|
|
52
|
+
* @returns A sink that writes to the file. The sink is also a disposable
|
|
53
|
+
* object that closes the file when disposed. If `nonBlocking` is
|
|
54
|
+
* enabled, returns a sink that also implements {@link AsyncDisposable}.
|
|
55
|
+
* @since 2.0.0
|
|
56
|
+
*/
|
|
57
|
+
declare function getTimeRotatingFileSink(options: TimeRotatingFileSinkOptions): Sink & Disposable;
|
|
58
|
+
declare function getTimeRotatingFileSink(options: TimeRotatingFileSinkOptions & {
|
|
59
|
+
nonBlocking: true;
|
|
60
|
+
}): Sink & AsyncDisposable;
|
|
61
|
+
//# sourceMappingURL=filesink.node.d.ts.map
|
|
62
|
+
//#endregion
|
|
63
|
+
//#endregion
|
|
64
|
+
export { getFileSink, getRotatingFileSink, getTimeRotatingFileSink };
|
|
65
|
+
//# sourceMappingURL=filesink.node.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesink.node.d.cts","names":["AsyncRotatingFileSinkDriver","FileSinkOptions","RotatingFileSinkDriver","RotatingFileSinkOptions","AsyncTimeRotatingFileSinkDriver","TimeRotatingFileSinkDriver","TimeRotatingFileSinkOptions","Sink","nodeDriver","nodeAsyncDriver","nodeTimeDriver","nodeAsyncTimeDriver","getFileSink","Disposable","AsyncDisposable","getRotatingFileSink","getTimeRotatingFileSink"],"sources":["../filesink.node.d.ts"],"sourcesContent":["import { AsyncRotatingFileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions } from \"./filesink.base.js\";\nimport { AsyncTimeRotatingFileSinkDriver, TimeRotatingFileSinkDriver, TimeRotatingFileSinkOptions } from \"./timefilesink.js\";\nimport { Sink } from \"@logtape/logtape\";\n\n//#region src/filesink.node.d.ts\n\n/**\n * A Node.js-specific file sink driver.\n */\ndeclare const nodeDriver: RotatingFileSinkDriver<number | void>;\n/**\n * A Node.js-specific async file sink driver.\n * @since 1.0.0\n */\ndeclare const nodeAsyncDriver: AsyncRotatingFileSinkDriver<number | void>;\n/**\n * A Node.js-specific time-rotating file sink driver.\n * @since 2.0.0\n */\ndeclare const nodeTimeDriver: TimeRotatingFileSinkDriver<number | void>;\n/**\n * A Node.js-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\ndeclare const nodeAsyncTimeDriver: AsyncTimeRotatingFileSinkDriver<number | void>;\n/**\n * Get a file sink.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\ndeclare function getFileSink(path: string, options?: FileSinkOptions): Sink & Disposable;\ndeclare function getFileSink(path: string, options: FileSinkOptions & {\n nonBlocking: true;\n}): Sink & AsyncDisposable;\n/**\n * Get a rotating file sink.\n *\n * This sink writes log records to a file, and rotates the file when it reaches\n * the `maxSize`. The rotated files are named with the original file name\n * followed by a dot and a number, starting from 1. The number is incremented\n * for each rotation, and the maximum number of files to keep is `maxFiles`.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param path A path to the file to write to.\n * @param options The options for the sink and the file driver.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is enabled,\n * returns a sink that also implements {@link AsyncDisposable}.\n */\ndeclare function getRotatingFileSink(path: string, options?: RotatingFileSinkOptions): Sink & Disposable;\ndeclare function getRotatingFileSink(path: string, options: RotatingFileSinkOptions & {\n nonBlocking: true;\n}): Sink & AsyncDisposable;\n/**\n * Get a time-rotating file sink.\n *\n * This sink writes log records to a file in a directory, rotating to a new\n * file based on time intervals. The filename is generated based on the\n * current date/time and the configured interval.\n *\n * Note that this function is unavailable in the browser.\n *\n * @param options The options for the sink.\n * @returns A sink that writes to the file. The sink is also a disposable\n * object that closes the file when disposed. If `nonBlocking` is\n * enabled, returns a sink that also implements {@link AsyncDisposable}.\n * @since 2.0.0\n */\ndeclare function getTimeRotatingFileSink(options: TimeRotatingFileSinkOptions): Sink & Disposable;\ndeclare function getTimeRotatingFileSink(options: TimeRotatingFileSinkOptions & {\n nonBlocking: true;\n}): Sink & AsyncDisposable;\n//# sourceMappingURL=filesink.node.d.ts.map\n//#endregion\nexport { getFileSink, getRotatingFileSink, getTimeRotatingFileSink, nodeAsyncDriver, nodeAsyncTimeDriver, nodeDriver, nodeTimeDriver };\n//# sourceMappingURL=filesink.node.d.ts.map"],"mappings":";;;;;;;;;AA2D0B;AAAA;;;;;AAgBuE;AAAA;iBAvChFY,WAAAA,CAwCuB,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAxCaX,eAwCb,CAAA,EAxC+BM,IAwC/B,GAxCsCM,UAwCtC;iBAvCvBD,WAAAA,CAuCiCN,IAAAA,EAAAA,MAAAA,EAAAA,OAAAA,EAvCEL,eAuCFK,GAAAA;EAA2B,WAEzEC,EAAAA,IAAAA;CAAI,CAAA,EAvCJA,IAuCOO,GAvCAA,eAuCAA;AAAe;;;;;;;;;;;;;;;;iBAtBTC,mBAAAA,yBAA4CZ,0BAA0BI,OAAOM;iBAC7EE,mBAAAA,wBAA2CZ;;IAExDI,OAAOO;;;;;;;;;;;;;;;;iBAgBME,uBAAAA,UAAiCV,8BAA8BC,OAAOM;iBACtEG,uBAAAA,UAAiCV;;IAE9CC,OAAOO"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FileSinkOptions } from "./filesink.base.cjs";
|
|
2
|
+
|
|
3
|
+
//#region dist/timefilesink.d.ts
|
|
4
|
+
|
|
5
|
+
//#region src/timefilesink.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The rotation interval for time-based file sinks.
|
|
8
|
+
*/
|
|
9
|
+
type TimeRotationInterval = "hourly" | "daily" | "weekly";
|
|
10
|
+
/**
|
|
11
|
+
* Options for the {@link getBaseTimeRotatingFileSink} function.
|
|
12
|
+
*/
|
|
13
|
+
interface TimeRotatingFileSinkOptions extends Omit<FileSinkOptions, "lazy"> {
|
|
14
|
+
/**
|
|
15
|
+
* The directory to write log files to.
|
|
16
|
+
*/
|
|
17
|
+
directory: string;
|
|
18
|
+
/**
|
|
19
|
+
* A function that generates the filename for the log file based on the date.
|
|
20
|
+
* Default depends on `interval`:
|
|
21
|
+
* - `"daily"`: `YYYY-MM-DD.log` (e.g., `2025-01-15.log`)
|
|
22
|
+
* - `"hourly"`: `YYYY-MM-DD-HH.log` (e.g., `2025-01-15-09.log`)
|
|
23
|
+
* - `"weekly"`: `YYYY-WW.log` (e.g., `2025-W03.log`)
|
|
24
|
+
*/
|
|
25
|
+
filename?: (date: Date) => string;
|
|
26
|
+
/**
|
|
27
|
+
* The rotation interval. Defaults to `"daily"`.
|
|
28
|
+
*/
|
|
29
|
+
interval?: TimeRotationInterval;
|
|
30
|
+
/**
|
|
31
|
+
* The maximum age of log files in milliseconds. Files older than this
|
|
32
|
+
* will be deleted. If not specified, old files are not deleted.
|
|
33
|
+
*/
|
|
34
|
+
maxAgeMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A platform-specific time-rotating file sink driver.
|
|
38
|
+
*/
|
|
39
|
+
//#endregion
|
|
40
|
+
export { TimeRotatingFileSinkOptions };
|
|
41
|
+
//# sourceMappingURL=timefilesink.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timefilesink.d.cts","names":["AsyncFileSinkDriver","FileSinkDriver","FileSinkOptions","Sink","TimeRotationInterval","TimeRotatingFileSinkOptions","Date","Omit","TimeRotatingFileSinkDriver","TFile","AsyncTimeRotatingFileSinkDriver"],"sources":["../timefilesink.d.ts"],"sourcesContent":["import { AsyncFileSinkDriver, FileSinkDriver, FileSinkOptions } from \"./filesink.base.js\";\nimport { Sink } from \"@logtape/logtape\";\n\n//#region src/timefilesink.d.ts\n\n/**\n * The rotation interval for time-based file sinks.\n */\ntype TimeRotationInterval = \"hourly\" | \"daily\" | \"weekly\";\n/**\n * Options for the {@link getBaseTimeRotatingFileSink} function.\n */\ninterface TimeRotatingFileSinkOptions extends Omit<FileSinkOptions, \"lazy\"> {\n /**\n * The directory to write log files to.\n */\n directory: string;\n /**\n * A function that generates the filename for the log file based on the date.\n * Default depends on `interval`:\n * - `\"daily\"`: `YYYY-MM-DD.log` (e.g., `2025-01-15.log`)\n * - `\"hourly\"`: `YYYY-MM-DD-HH.log` (e.g., `2025-01-15-09.log`)\n * - `\"weekly\"`: `YYYY-WW.log` (e.g., `2025-W03.log`)\n */\n filename?: (date: Date) => string;\n /**\n * The rotation interval. Defaults to `\"daily\"`.\n */\n interval?: TimeRotationInterval;\n /**\n * The maximum age of log files in milliseconds. Files older than this\n * will be deleted. If not specified, old files are not deleted.\n */\n maxAgeMs?: number;\n}\n/**\n * A platform-specific time-rotating file sink driver.\n */\ninterface TimeRotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: {\n recursive?: boolean;\n }): void;\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n/**\n * A platform-specific async time-rotating file sink driver.\n * @since 2.0.0\n */\ninterface AsyncTimeRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {\n /**\n * Read the contents of a directory.\n * @param path A path to the directory.\n * @returns An array of filenames in the directory.\n */\n readdirSync(path: string): string[];\n /**\n * Delete a file.\n * @param path A path to the file to delete.\n */\n unlinkSync(path: string): void;\n /**\n * Create a directory if it doesn't exist.\n * @param path A path to the directory to create.\n * @param options Options for directory creation.\n */\n mkdirSync(path: string, options?: {\n recursive?: boolean;\n }): void;\n /**\n * Join path segments.\n * @param paths Path segments to join.\n * @returns The joined path.\n */\n joinPath(...paths: string[]): string;\n}\n/**\n * Get the ISO week number of a date.\n * @param date The date to get the week number of.\n * @returns The ISO week number (1-53).\n */\n//#endregion\nexport { AsyncTimeRotatingFileSinkDriver, TimeRotatingFileSinkDriver, TimeRotatingFileSinkOptions, TimeRotationInterval };\n//# sourceMappingURL=timefilesink.d.ts.map"],"mappings":";;;;AACwC;;;;KAOnCI,oBAAAA,GAoBQA,QAAAA,GAAAA,OAAAA,GAAAA,QAAAA;;AAhBqC;;UAAxCC,2BAAAA,SAAoCE,KAAKL;;;;;;;;;;;;oBAY/BI;;;;aAIPF"}
|
package/dist/filesink.base.cjs
CHANGED
|
@@ -2,6 +2,9 @@ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
|
2
2
|
const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
|
|
3
3
|
|
|
4
4
|
//#region src/filesink.base.ts
|
|
5
|
+
function isMetaLoggerRecord(record) {
|
|
6
|
+
return record.category.length === 2 && record.category[0] === "logtape" && record.category[1] === "meta";
|
|
7
|
+
}
|
|
5
8
|
/**
|
|
6
9
|
* Adaptive flush strategy that dynamically adjusts buffer thresholds
|
|
7
10
|
* based on recent flush patterns for optimal performance.
|
|
@@ -13,8 +16,10 @@ var AdaptiveFlushStrategy = class {
|
|
|
13
16
|
avgFlushInterval;
|
|
14
17
|
maxHistorySize = 10;
|
|
15
18
|
baseThreshold;
|
|
19
|
+
baseInterval;
|
|
16
20
|
constructor(baseThreshold, baseInterval) {
|
|
17
21
|
this.baseThreshold = baseThreshold;
|
|
22
|
+
this.baseInterval = baseInterval;
|
|
18
23
|
this.avgFlushSize = baseThreshold;
|
|
19
24
|
this.avgFlushInterval = baseInterval;
|
|
20
25
|
}
|
|
@@ -53,7 +58,8 @@ var AdaptiveFlushStrategy = class {
|
|
|
53
58
|
return Math.max(Math.min(4096, this.baseThreshold / 2), Math.min(64 * 1024, this.baseThreshold * adaptiveFactor));
|
|
54
59
|
}
|
|
55
60
|
calculateAdaptiveInterval() {
|
|
56
|
-
if (this.
|
|
61
|
+
if (this.baseInterval <= 0) return 0;
|
|
62
|
+
if (this.avgFlushInterval <= 0) return this.baseInterval;
|
|
57
63
|
if (this.recentFlushTimes.length < 3) return this.avgFlushInterval;
|
|
58
64
|
const variance = this.calculateVariance(this.recentFlushTimes);
|
|
59
65
|
const stabilityFactor = Math.min(2, Math.max(.5, 1e3 / variance));
|
|
@@ -203,18 +209,22 @@ function getBaseFileSink(path, options) {
|
|
|
203
209
|
}
|
|
204
210
|
const sink = (record) => {
|
|
205
211
|
if (fd == null) fd = options.openSync(path);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
let formattedRecord;
|
|
213
|
+
let encodedRecord;
|
|
214
|
+
if (byteBuffer.isEmpty()) {
|
|
215
|
+
formattedRecord = formatter(record);
|
|
216
|
+
encodedRecord = encoder.encode(formattedRecord);
|
|
217
|
+
if (encodedRecord.length < 200) {
|
|
218
|
+
options.writeSync(fd, encodedRecord);
|
|
211
219
|
options.flushSync(fd);
|
|
212
|
-
|
|
220
|
+
const currentTime = Date.now();
|
|
221
|
+
adaptiveStrategy.recordFlush(encodedRecord.length, currentTime - lastFlushTimestamp);
|
|
222
|
+
lastFlushTimestamp = currentTime;
|
|
213
223
|
return;
|
|
214
224
|
}
|
|
215
225
|
}
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
formattedRecord ??= formatter(record);
|
|
227
|
+
encodedRecord ??= encoder.encode(formattedRecord);
|
|
218
228
|
byteBuffer.append(encodedRecord);
|
|
219
229
|
if (bufferSize <= 0) flushBuffer$1();
|
|
220
230
|
else {
|
|
@@ -236,24 +246,53 @@ function getBaseFileSink(path, options) {
|
|
|
236
246
|
let disposed = false;
|
|
237
247
|
let activeFlush = null;
|
|
238
248
|
let flushTimer = null;
|
|
249
|
+
let bufferedNonMetaRecord = false;
|
|
250
|
+
function reportFlushError(error) {
|
|
251
|
+
try {
|
|
252
|
+
(0, __logtape_logtape.getLogger)(["logtape", "meta"]).warn("Non-blocking file sink flush failed for {path}: {error}", {
|
|
253
|
+
error,
|
|
254
|
+
path
|
|
255
|
+
});
|
|
256
|
+
} catch {}
|
|
257
|
+
}
|
|
239
258
|
async function flushBuffer() {
|
|
240
259
|
if (fd == null || byteBuffer.isEmpty()) return;
|
|
241
260
|
const flushSize = byteBuffer.size();
|
|
242
261
|
const currentTime = Date.now();
|
|
243
262
|
const timeSinceLastFlush = currentTime - lastFlushTimestamp;
|
|
244
263
|
const chunks = byteBuffer.flush();
|
|
264
|
+
const suppressErrorReport = !bufferedNonMetaRecord;
|
|
265
|
+
bufferedNonMetaRecord = false;
|
|
245
266
|
try {
|
|
246
267
|
if (asyncOptions.writeMany && chunks.length > 1) await asyncOptions.writeMany(fd, chunks);
|
|
247
268
|
else for (const chunk of chunks) asyncOptions.writeSync(fd, chunk);
|
|
248
269
|
await asyncOptions.flush(fd);
|
|
249
270
|
adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
|
|
250
271
|
lastFlushTimestamp = currentTime;
|
|
251
|
-
} catch {
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!suppressErrorReport) reportFlushError(error);
|
|
274
|
+
}
|
|
252
275
|
}
|
|
253
276
|
function scheduleFlush() {
|
|
254
277
|
if (activeFlush || disposed) return;
|
|
255
278
|
activeFlush = flushBuffer().finally(() => {
|
|
256
279
|
activeFlush = null;
|
|
280
|
+
if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function scheduleDirectFlush(flushSize, suppressErrorReport) {
|
|
284
|
+
if (activeFlush || disposed || fd == null) return;
|
|
285
|
+
const flushFd = fd;
|
|
286
|
+
const startedAt = Date.now();
|
|
287
|
+
const timeSinceLastFlush = startedAt - lastFlushTimestamp;
|
|
288
|
+
activeFlush = Promise.resolve().then(() => asyncOptions.flush(flushFd)).then(() => {
|
|
289
|
+
adaptiveStrategy.recordFlush(flushSize, timeSinceLastFlush);
|
|
290
|
+
lastFlushTimestamp = Date.now();
|
|
291
|
+
}).catch((error) => {
|
|
292
|
+
if (!suppressErrorReport) reportFlushError(error);
|
|
293
|
+
}).finally(() => {
|
|
294
|
+
activeFlush = null;
|
|
295
|
+
if (!disposed && !byteBuffer.isEmpty()) scheduleFlush();
|
|
257
296
|
});
|
|
258
297
|
}
|
|
259
298
|
function startFlushTimer() {
|
|
@@ -265,19 +304,21 @@ function getBaseFileSink(path, options) {
|
|
|
265
304
|
const nonBlockingSink = (record) => {
|
|
266
305
|
if (disposed) return;
|
|
267
306
|
if (fd == null) fd = asyncOptions.openSync(path);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
let formattedRecord;
|
|
308
|
+
let encodedRecord;
|
|
309
|
+
if (byteBuffer.isEmpty() && !activeFlush) {
|
|
310
|
+
formattedRecord = formatter(record);
|
|
311
|
+
encodedRecord = encoder.encode(formattedRecord);
|
|
312
|
+
if (encodedRecord.length < 200) {
|
|
313
|
+
asyncOptions.writeSync(fd, encodedRecord);
|
|
314
|
+
scheduleDirectFlush(encodedRecord.length, isMetaLoggerRecord(record));
|
|
275
315
|
return;
|
|
276
316
|
}
|
|
277
317
|
}
|
|
278
|
-
|
|
279
|
-
|
|
318
|
+
formattedRecord ??= formatter(record);
|
|
319
|
+
encodedRecord ??= encoder.encode(formattedRecord);
|
|
280
320
|
byteBuffer.append(encodedRecord);
|
|
321
|
+
bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
|
|
281
322
|
if (bufferSize <= 0) scheduleFlush();
|
|
282
323
|
else {
|
|
283
324
|
const timeSinceLastFlush = record.timestamp - lastFlushTimestamp;
|
|
@@ -292,6 +333,7 @@ function getBaseFileSink(path, options) {
|
|
|
292
333
|
clearInterval(flushTimer);
|
|
293
334
|
flushTimer = null;
|
|
294
335
|
}
|
|
336
|
+
if (activeFlush !== null) await activeFlush;
|
|
295
337
|
await flushBuffer();
|
|
296
338
|
if (fd !== null) try {
|
|
297
339
|
await asyncOptions.close(fd);
|
|
@@ -300,6 +342,11 @@ function getBaseFileSink(path, options) {
|
|
|
300
342
|
};
|
|
301
343
|
return nonBlockingSink;
|
|
302
344
|
}
|
|
345
|
+
function isFileNotFoundError(error) {
|
|
346
|
+
if (!(error instanceof Error)) return false;
|
|
347
|
+
const errorWithCode = error;
|
|
348
|
+
return errorWithCode.code === "ENOENT" || error.name === "NotFound";
|
|
349
|
+
}
|
|
303
350
|
function getBaseRotatingFileSink(path, options) {
|
|
304
351
|
const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
|
|
305
352
|
const encoder = options.encoder ?? new TextEncoder();
|
|
@@ -307,6 +354,7 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
307
354
|
const maxFiles = options.maxFiles ?? 5;
|
|
308
355
|
const bufferSize = options.bufferSize ?? 1024 * 8;
|
|
309
356
|
const flushInterval = options.flushInterval ?? 5e3;
|
|
357
|
+
if (maxFiles <= 0 && options.unlinkSync == null) throw new TypeError("maxFiles <= 0 requires unlinkSync support.");
|
|
310
358
|
let offset = 0;
|
|
311
359
|
try {
|
|
312
360
|
const stat = options.statSync(path);
|
|
@@ -319,6 +367,27 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
319
367
|
return offset + bytes.length > maxSize;
|
|
320
368
|
}
|
|
321
369
|
function performRollover() {
|
|
370
|
+
if (maxFiles <= 0) {
|
|
371
|
+
const unlinkSync = options.unlinkSync;
|
|
372
|
+
if (unlinkSync == null) return;
|
|
373
|
+
options.closeSync(fd);
|
|
374
|
+
try {
|
|
375
|
+
unlinkSync(path);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (!isFileNotFoundError(error)) {
|
|
378
|
+
try {
|
|
379
|
+
offset = options.statSync(path).size;
|
|
380
|
+
} catch {
|
|
381
|
+
offset = 0;
|
|
382
|
+
}
|
|
383
|
+
fd = options.openSync(path);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
offset = 0;
|
|
388
|
+
fd = options.openSync(path);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
322
391
|
options.closeSync(fd);
|
|
323
392
|
for (let i = maxFiles - 1; i > 0; i--) {
|
|
324
393
|
const oldPath = `${path}.${i}`;
|
|
@@ -359,10 +428,21 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
359
428
|
let disposed = false;
|
|
360
429
|
let activeFlush = null;
|
|
361
430
|
let flushTimer = null;
|
|
431
|
+
let bufferedNonMetaRecord = false;
|
|
432
|
+
function reportFlushError(error) {
|
|
433
|
+
try {
|
|
434
|
+
(0, __logtape_logtape.getLogger)(["logtape", "meta"]).warn("Non-blocking rotating file sink flush failed for {path}: {error}", {
|
|
435
|
+
error,
|
|
436
|
+
path
|
|
437
|
+
});
|
|
438
|
+
} catch {}
|
|
439
|
+
}
|
|
362
440
|
async function flushBuffer() {
|
|
363
441
|
if (buffer.length === 0) return;
|
|
364
442
|
const data = buffer;
|
|
365
443
|
buffer = "";
|
|
444
|
+
const suppressErrorReport = !bufferedNonMetaRecord;
|
|
445
|
+
bufferedNonMetaRecord = false;
|
|
366
446
|
try {
|
|
367
447
|
const bytes = encoder.encode(data);
|
|
368
448
|
if (shouldRollover(bytes)) performRollover();
|
|
@@ -370,7 +450,9 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
370
450
|
await asyncOptions.flush(fd);
|
|
371
451
|
offset += bytes.length;
|
|
372
452
|
lastFlushTimestamp = Date.now();
|
|
373
|
-
} catch {
|
|
453
|
+
} catch (error) {
|
|
454
|
+
if (!suppressErrorReport) reportFlushError(error);
|
|
455
|
+
}
|
|
374
456
|
}
|
|
375
457
|
function scheduleFlush() {
|
|
376
458
|
if (activeFlush || disposed) return;
|
|
@@ -387,6 +469,7 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
387
469
|
const nonBlockingSink = (record) => {
|
|
388
470
|
if (disposed) return;
|
|
389
471
|
buffer += formatter(record);
|
|
472
|
+
bufferedNonMetaRecord ||= !isMetaLoggerRecord(record);
|
|
390
473
|
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
391
474
|
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
392
475
|
if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
|
|
@@ -398,6 +481,7 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
398
481
|
clearInterval(flushTimer);
|
|
399
482
|
flushTimer = null;
|
|
400
483
|
}
|
|
484
|
+
if (activeFlush !== null) await activeFlush;
|
|
401
485
|
await flushBuffer();
|
|
402
486
|
try {
|
|
403
487
|
await asyncOptions.close(fd);
|
package/dist/filesink.base.d.cts
CHANGED
|
@@ -136,6 +136,11 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
136
136
|
* @param newPath A path to be renamed to.
|
|
137
137
|
*/
|
|
138
138
|
renameSync(oldPath: string, newPath: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* Delete a file.
|
|
141
|
+
* @param path A path to the file to delete.
|
|
142
|
+
*/
|
|
143
|
+
unlinkSync?(path: string): void;
|
|
139
144
|
}
|
|
140
145
|
/**
|
|
141
146
|
* A platform-specific async rotating file sink driver.
|
|
@@ -156,6 +161,11 @@ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile>
|
|
|
156
161
|
* @param newPath A path to be renamed to.
|
|
157
162
|
*/
|
|
158
163
|
renameSync(oldPath: string, newPath: string): void;
|
|
164
|
+
/**
|
|
165
|
+
* Delete a file.
|
|
166
|
+
* @param path A path to the file to delete.
|
|
167
|
+
*/
|
|
168
|
+
unlinkSync?(path: string): void;
|
|
159
169
|
}
|
|
160
170
|
/**
|
|
161
171
|
* Get a platform-independent rotating file sink.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAgRA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAiTlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AA0BrE;;;AACU,UAnYO,cAmYP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BA9XH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA8RH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;UA0BrD,2CACP,oBAAoB"}
|
package/dist/filesink.base.d.ts
CHANGED
|
@@ -136,6 +136,11 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
136
136
|
* @param newPath A path to be renamed to.
|
|
137
137
|
*/
|
|
138
138
|
renameSync(oldPath: string, newPath: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* Delete a file.
|
|
141
|
+
* @param path A path to the file to delete.
|
|
142
|
+
*/
|
|
143
|
+
unlinkSync?(path: string): void;
|
|
139
144
|
}
|
|
140
145
|
/**
|
|
141
146
|
* A platform-specific async rotating file sink driver.
|
|
@@ -156,6 +161,11 @@ interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile>
|
|
|
156
161
|
* @param newPath A path to be renamed to.
|
|
157
162
|
*/
|
|
158
163
|
renameSync(oldPath: string, newPath: string): void;
|
|
164
|
+
/**
|
|
165
|
+
* Delete a file.
|
|
166
|
+
* @param path A path to the file to delete.
|
|
167
|
+
*/
|
|
168
|
+
unlinkSync?(path: string): void;
|
|
159
169
|
}
|
|
160
170
|
/**
|
|
161
171
|
* Get a platform-independent rotating file sink.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../src/filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAgRA;AAuCiB,UAvCA,eAAA,SAAwB,iBAuCV,CAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;;;AAoBnB;AAQrB;;EAAoC,UAA+B,CAAA,EAAA,MAAA;EAAK;;;;;;;EAmB7C,aAnByB,CAAA,EAAA,MAAA;EAAc;AAiTlE;;;;AAAqD;AAerD;;EAAuC,WAA+B,CAAA,EAAA,OAAA;;AAAD;AA0BrE;;;AACU,UAnYO,cAmYP,CAAA,KAAA,CAAA,CAAA;EAAmB;;;;0BA9XH;;;;;;gBAOV,cAAc;;;;;;;qBAQT,eAAe;;;;;gBAMpB;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;;;iBAOlD,eAAe,eAAe;;;;;YAMnC,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA8RH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;UA0BrD,2CACP,oBAAoB"}
|