backlib 0.5.0 → 0.5.2-SNAPSHOT.3

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.
@@ -1,6 +1,7 @@
1
1
  import { LogWriter } from './index.js';
2
- export declare type OnFileCompleted = (file: string) => Promise<void>;
3
2
  export declare type FileNameProvider = (rev: number) => string;
3
+ export declare type FileHeaderProvider = () => string | null;
4
+ export declare type OnFileCompleted = (file: string) => Promise<void>;
4
5
  /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
5
6
  export declare type RecordSerializer<R> = (rec: R) => string | null;
6
7
  export interface FileWriterOptions<R> {
@@ -12,7 +13,11 @@ export interface FileWriterOptions<R> {
12
13
  maxTime: number;
13
14
  /** Optional fileName generator for the new log file name (MUST BE UNIQUE for this dir) */
14
15
  fileNameProvider?: FileNameProvider;
16
+ /** Called when the log file is first created (useful to create the csv header for csv format) */
17
+ fileHeaderProvider?: FileHeaderProvider;
18
+ /** Optional recordSerializer to file. By default, JSON.serializer() (new line json) */
15
19
  recordSerializer?: RecordSerializer<R>;
20
+ /** Called when a log file is completed (i.e., new entries will go to another file) */
16
21
  onFileCompleted?: OnFileCompleted;
17
22
  }
18
23
  export declare class FileWriter<R> implements LogWriter<R> {
@@ -20,6 +25,7 @@ export declare class FileWriter<R> implements LogWriter<R> {
20
25
  private maxCount;
21
26
  private maxTime;
22
27
  private fileNameProvider;
28
+ private fileHeaderProvider;
23
29
  private recordSerializer;
24
30
  private onFileCompleted?;
25
31
  private _init;
@@ -27,10 +33,11 @@ export declare class FileWriter<R> implements LogWriter<R> {
27
33
  private count;
28
34
  private nextUpload;
29
35
  private lastUpload?;
30
- private file?;
36
+ private filePath?;
37
+ private fileHandle?;
31
38
  constructor(opts: FileWriterOptions<R>);
32
39
  private init;
33
- /** Update the revision file */
40
+ /** Update the revision file and create the new file and call onFileStart */
34
41
  private rev;
35
42
  /** IMPLEMENTATION of the FileWriter interface */
36
43
  writeRec(rec: R): Promise<void>;
@@ -1,5 +1,6 @@
1
+ import { appendFileSync, openSync } from 'fs';
1
2
  import { pathExists } from 'fs-aux';
2
- import { appendFile, mkdir, rename } from 'fs/promises';
3
+ import { appendFile, mkdir, open, rename } from 'fs/promises';
3
4
  import * as Path from "path";
4
5
  import { isString } from 'utils-min';
5
6
  export class FileWriter {
@@ -12,32 +13,53 @@ export class FileWriter {
12
13
  this.maxTime = opts.maxTime;
13
14
  this.dir = opts.dir;
14
15
  this.fileNameProvider = opts.fileNameProvider ?? defaultFileNameProvider;
16
+ this.fileHeaderProvider = opts.fileHeaderProvider ?? defaultFileHeaderProvider;
15
17
  this.onFileCompleted = opts.onFileCompleted;
16
18
  this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
17
19
  }
18
20
  async init() {
19
21
  if (!this._init) {
20
22
  await mkdir(this.dir, { recursive: true });
21
- this.rev();
23
+ await this.rev();
22
24
  this._init = true;
23
25
  }
24
26
  }
25
- /** Update the revision file */
26
- rev() {
27
+ /** Update the revision file and create the new file and call onFileStart */
28
+ async rev() {
27
29
  this.count = 0;
28
30
  this._rev = this._rev + 1;
31
+ // make sure to reset the file info
32
+ this.filePath = undefined;
33
+ this.fileHandle = undefined;
34
+ // -- CREATE the new file
29
35
  const fileName = this.fileNameProvider(this._rev);
30
- this.file = Path.join(this.dir, fileName);
36
+ this.filePath = Path.join(this.dir, fileName);
37
+ // create and add the content Sync to make sure header is always first
38
+ let fd = openSync(this.filePath, 'a');
39
+ let fileHeader = this.fileHeaderProvider();
40
+ if (fileHeader != null) {
41
+ appendFileSync(fd, fileHeader);
42
+ }
43
+ else {
44
+ // make sure it is created (might not be needed)
45
+ appendFileSync(fd, '');
46
+ }
47
+ // -- CREATE the file handler
48
+ this.fileHandle = await open(this.filePath, 'a');
31
49
  }
32
50
  /** IMPLEMENTATION of the FileWriter interface */
33
51
  async writeRec(rec) {
34
52
  if (!this._init) {
35
53
  await this.init();
36
54
  }
55
+ if (this.fileHandle == null) {
56
+ console.log(`BACKLIB ERROR - FileHandle for log file not ready yet ${this.filePath}`);
57
+ return;
58
+ }
37
59
  const str = this.recordSerializer(rec);
38
60
  if (str != null) {
39
61
  const strWithNl = str + '\n'; // add the new line
40
- await appendFile(this.file, strWithNl);
62
+ await appendFile(this.fileHandle, strWithNl);
41
63
  }
42
64
  // add count
43
65
  this.count = this.count + 1;
@@ -59,12 +81,15 @@ export class FileWriter {
59
81
  }
60
82
  }
61
83
  async endFile() {
62
- const file = this.file;
84
+ const file = this.filePath;
85
+ const fileHandle = this.fileHandle;
63
86
  // we rev just before to make sure other logs will happen on new files
64
- this.rev();
87
+ await this.rev();
88
+ // process old file
65
89
  try {
66
90
  const exists = await pathExists(file);
67
91
  if (exists) {
92
+ await fileHandle?.close();
68
93
  if (this.onFileCompleted) {
69
94
  try {
70
95
  await this.onFileCompleted(file);
@@ -92,6 +117,11 @@ export class FileWriter {
92
117
  function defaultSerializer(rec) {
93
118
  return isString(rec) ? rec : JSON.stringify(rec);
94
119
  }
120
+ function defaultFileHeaderProvider() {
121
+ // Return null, meaning, no header
122
+ return null;
123
+ // For CSV, custom onFileStart will need to set the header
124
+ }
95
125
  function defaultFileNameProvider(rev) {
96
126
  const date = new Date().toISOString().replace(/[T:.]/g, "-").slice(0, -1);
97
127
  const revStr = `${rev}`.padStart(5, '0');
@@ -1 +1 @@
1
- {"version":3,"file":"log-file-writer.js","sourceRoot":"","sources":["../src/log-file-writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"log-file-writer.js","sourceRoot":"","sources":["../src/log-file-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAc,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AA6BrC,MAAM,OAAO,UAAU;IAmBrB,YAAY,IAA0B;QAT9B,UAAK,GAAG,KAAK,CAAC;QACd,SAAI,GAAG,CAAC,CAAC;QACT,UAAK,GAAG,CAAC,CAAC;QACV,eAAU,GAAkB,IAAI,CAAC,CAAC,+BAA+B;QAOvE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,uBAAuB,CAAC;QACzE,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,IAAI,yBAAyB,CAAC;QAC/E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;QAC5C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,CAAC;IACrE,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;SACnB;IACH,CAAC;IAED,4EAA4E;IACpE,KAAK,CAAC,GAAG;QACf,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAE1B,mCAAmC;QACnC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAE5B,yBAAyB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAE9C,sEAAsE;QACtE,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,UAAU,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC3C,IAAI,UAAU,IAAI,IAAI,EAAE;YACtB,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;SAChC;aAAM;YACL,gDAAgD;YAChD,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SACxB;QAED,6BAA6B;QAC7B,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAGD,iDAAiD;IACjD,KAAK,CAAC,QAAQ,CAAC,GAAM;QAEnB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;SACnB;QAED,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE;YAC3B,OAAO,CAAC,GAAG,CAAC,yDAAyD,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtF,OAAO;SACR;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,GAAG,IAAI,IAAI,EAAE;YACf,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,mBAAmB;YACjD,MAAM,UAAU,CAAC,IAAI,CAAC,UAAW,EAAE,SAAS,CAAC,CAAC;SAC/C;QAED,YAAY;QACZ,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QAE5B,uCAAuC;QACvC,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE;YAC9B,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;SACtB;QACD,0EAA0E;aACrE,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE;YACjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,QAAQ;YAE/C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;YAE7B,UAAU,CAAC,KAAK,IAAI,EAAE;gBACpB,kIAAkI;gBAClI,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU,EAAE;oBAClC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;iBACtB;YACH,CAAC,EAAE,SAAS,CAAC,CAAC;SACf;IAEH,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAS,CAAC;QAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAEnC,sEAAsE;QACtE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAEjB,mBAAmB;QACnB,IAAI;YACF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,MAAM,EAAE;gBACV,MAAM,UAAU,EAAE,KAAK,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,eAAe,EAAE;oBACxB,IAAI;wBACF,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;qBAClC;oBAAC,OAAO,EAAO,EAAE;wBAChB,OAAO,CAAC,GAAG,CAAC,2CAA2C,IAAI,iCAAiC,EAAE,EAAE,CAAC,CAAC;qBACnG;iBACF;aACF;iBAAM;gBACL,OAAO,CAAC,GAAG,CAAC,2DAA2D,IAAI,2BAA2B,CAAC,CAAC;aACzG;SAEF;QACD,kDAAkD;QAClD,OAAO,EAAO,EAAE;YACd,OAAO,CAAC,GAAG,CAAC,uEAAuE,IAAI,KAAK,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1G,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC;SACrC;QAED,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;CAEF;AAED,yBAAyB;AAEzB,SAAS,iBAAiB,CAAI,GAAM;IAClC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,yBAAyB;IAChC,kCAAkC;IAClC,OAAO,IAAI,CAAC;IAEZ,0DAA0D;AAC5D,CAAC;AAED,SAAS,uBAAuB,CAAC,GAAW;IAC1C,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,OAAO,YAAY,IAAI,IAAI,MAAM,MAAM,CAAC;AAC1C,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "backlib",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.5.2-SNAPSHOT.3",
5
5
  "description": "Minimalist library for backend services",
6
6
  "main": "dist/index.js",
7
7
  "engines": {
@@ -1,11 +1,13 @@
1
+ import { appendFileSync, openSync } from 'fs';
1
2
  import { pathExists } from 'fs-aux';
2
- import { appendFile, mkdir, rename } from 'fs/promises';
3
+ import { appendFile, FileHandle, mkdir, open, rename } from 'fs/promises';
3
4
  import * as Path from "path";
4
5
  import { isString } from 'utils-min';
5
6
  import { LogWriter } from './index.js';
6
7
 
7
- export type OnFileCompleted = (file: string) => Promise<void>;
8
8
  export type FileNameProvider = (rev: number) => string;
9
+ export type FileHeaderProvider = () => string | null; // must be sync
10
+ export type OnFileCompleted = (file: string) => Promise<void>;
9
11
 
10
12
  /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
11
13
  export type RecordSerializer<R> = (rec: R) => string | null;
@@ -20,9 +22,11 @@ export interface FileWriterOptions<R> {
20
22
 
21
23
  /** Optional fileName generator for the new log file name (MUST BE UNIQUE for this dir) */
22
24
  fileNameProvider?: FileNameProvider,
23
- /* Optional recordSerializer to file. By default, JSON.serializer() (new line json) */
25
+ /** Called when the log file is first created (useful to create the csv header for csv format) */
26
+ fileHeaderProvider?: FileHeaderProvider;
27
+ /** Optional recordSerializer to file. By default, JSON.serializer() (new line json) */
24
28
  recordSerializer?: RecordSerializer<R>;
25
- /* Call when a log file is completed (i.e., new entries will go to another file) */
29
+ /** Called when a log file is completed (i.e., new entries will go to another file) */
26
30
  onFileCompleted?: OnFileCompleted;
27
31
 
28
32
  }
@@ -33,6 +37,7 @@ export class FileWriter<R> implements LogWriter<R> {
33
37
  private maxTime: number;
34
38
 
35
39
  private fileNameProvider: FileNameProvider;
40
+ private fileHeaderProvider: FileHeaderProvider;
36
41
  private recordSerializer: RecordSerializer<R>;
37
42
  private onFileCompleted?: OnFileCompleted;
38
43
 
@@ -42,13 +47,15 @@ export class FileWriter<R> implements LogWriter<R> {
42
47
  private nextUpload: number | null = null; // null means nothing scheduled
43
48
  private lastUpload?: number;
44
49
 
45
- private file?: string;
50
+ private filePath?: string;
51
+ private fileHandle?: FileHandle;
46
52
 
47
53
  constructor(opts: FileWriterOptions<R>) {
48
54
  this.maxCount = opts.maxCount;
49
55
  this.maxTime = opts.maxTime;
50
56
  this.dir = opts.dir;
51
57
  this.fileNameProvider = opts.fileNameProvider ?? defaultFileNameProvider;
58
+ this.fileHeaderProvider = opts.fileHeaderProvider ?? defaultFileHeaderProvider;
52
59
  this.onFileCompleted = opts.onFileCompleted;
53
60
  this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
54
61
  }
@@ -56,19 +63,36 @@ export class FileWriter<R> implements LogWriter<R> {
56
63
  private async init() {
57
64
  if (!this._init) {
58
65
  await mkdir(this.dir, { recursive: true });
59
- this.rev();
66
+ await this.rev();
60
67
  this._init = true;
61
68
  }
62
69
  }
63
70
 
64
- /** Update the revision file */
65
- private rev() {
71
+ /** Update the revision file and create the new file and call onFileStart */
72
+ private async rev() {
66
73
  this.count = 0;
67
74
  this._rev = this._rev + 1;
68
75
 
76
+ // make sure to reset the file info
77
+ this.filePath = undefined;
78
+ this.fileHandle = undefined;
79
+
80
+ // -- CREATE the new file
69
81
  const fileName = this.fileNameProvider(this._rev);
82
+ this.filePath = Path.join(this.dir, fileName);
83
+
84
+ // create and add the content Sync to make sure header is always first
85
+ let fd = openSync(this.filePath, 'a');
86
+ let fileHeader = this.fileHeaderProvider();
87
+ if (fileHeader != null) {
88
+ appendFileSync(fd, fileHeader);
89
+ } else {
90
+ // make sure it is created (might not be needed)
91
+ appendFileSync(fd, '');
92
+ }
70
93
 
71
- this.file = Path.join(this.dir, fileName)
94
+ // -- CREATE the file handler
95
+ this.fileHandle = await open(this.filePath, 'a');
72
96
  }
73
97
 
74
98
 
@@ -79,10 +103,14 @@ export class FileWriter<R> implements LogWriter<R> {
79
103
  await this.init();
80
104
  }
81
105
 
106
+ if (this.fileHandle == null) {
107
+ console.log(`BACKLIB ERROR - FileHandle for log file not ready yet ${this.filePath}`);
108
+ return;
109
+ }
82
110
  const str = this.recordSerializer(rec);
83
111
  if (str != null) {
84
112
  const strWithNl = str + '\n'; // add the new line
85
- await appendFile(this.file!, strWithNl);
113
+ await appendFile(this.fileHandle!, strWithNl);
86
114
  }
87
115
 
88
116
  // add count
@@ -110,13 +138,17 @@ export class FileWriter<R> implements LogWriter<R> {
110
138
  }
111
139
 
112
140
  private async endFile() {
113
- const file = this.file!;
141
+ const file = this.filePath!;
142
+ const fileHandle = this.fileHandle;
143
+
114
144
  // we rev just before to make sure other logs will happen on new files
115
- this.rev();
145
+ await this.rev();
116
146
 
147
+ // process old file
117
148
  try {
118
149
  const exists = await pathExists(file);
119
150
  if (exists) {
151
+ await fileHandle?.close();
120
152
  if (this.onFileCompleted) {
121
153
  try {
122
154
  await this.onFileCompleted(file);
@@ -148,6 +180,13 @@ function defaultSerializer<R>(rec: R): string {
148
180
  return isString(rec) ? rec : JSON.stringify(rec);
149
181
  }
150
182
 
183
+ function defaultFileHeaderProvider(): string | null {
184
+ // Return null, meaning, no header
185
+ return null;
186
+
187
+ // For CSV, custom onFileStart will need to set the header
188
+ }
189
+
151
190
  function defaultFileNameProvider(rev: number): string {
152
191
  const date = new Date().toISOString().replace(/[T:.]/g, "-").slice(0, -1);
153
192
  const revStr = `${rev}`.padStart(5, '0');