backlib 0.4.0 → 0.5.2-SNAPSHOT.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export * from './decorator-leaf-tracer.js';
2
- export * from './fs.js';
3
- export * from './log.js';
4
- export * from './utils.js';
1
+ export { newLeafTracer } from './decorator-leaf-tracer.js';
2
+ export { FileWriter } from './log-file-writer.js';
3
+ export type { FileNameProvider, FileWriterOptions, OnFileCompleted, RecordSerializer } from './log-file-writer.js';
4
+ export { BaseLog } from './log.js';
5
+ export type { LogOptions, LogWriter } from './log.js';
6
+ export { prompt } from './utils.js';
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
- export * from './decorator-leaf-tracer.js';
2
- export * from './fs.js';
3
- export * from './log.js';
4
- export * from './utils.js';
1
+ // Export decorator constructs
2
+ export { newLeafTracer } from './decorator-leaf-tracer.js';
3
+ // Export log-file-writer constructs
4
+ export { FileWriter } from './log-file-writer.js';
5
+ // Export log constructs
6
+ export { BaseLog } from './log.js';
7
+ // Export utils constructs
8
+ export { prompt } from './utils.js';
5
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,oCAAoC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,wBAAwB;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,0BAA0B;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { LogWriter } from './index.js';
2
+ export declare type FileNameProvider = (rev: number) => string;
3
+ export declare type FileHeaderProvider = () => string | null;
4
+ export declare type OnFileCompleted = (file: string) => Promise<void>;
5
+ /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
6
+ export declare type RecordSerializer<R> = (rec: R) => string | null;
7
+ export interface FileWriterOptions<R> {
8
+ /** Local directory in which the logs files will be saved */
9
+ dir: string;
10
+ /** maxCount of record before file is uploaded to destination */
11
+ maxCount: number;
12
+ /** max time (in seconds) before file is uploaded to destination (which ever comes first with maxCount) */
13
+ maxTime: number;
14
+ /** Optional fileName generator for the new log file name (MUST BE UNIQUE for this dir) */
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) */
19
+ recordSerializer?: RecordSerializer<R>;
20
+ /** Called when a log file is completed (i.e., new entries will go to another file) */
21
+ onFileCompleted?: OnFileCompleted;
22
+ }
23
+ export declare class FileWriter<R> implements LogWriter<R> {
24
+ private dir;
25
+ private maxCount;
26
+ private maxTime;
27
+ private fileNameProvider;
28
+ private fileHeaderProvider;
29
+ private recordSerializer;
30
+ private onFileCompleted?;
31
+ private _init;
32
+ private _rev;
33
+ private count;
34
+ private nextUpload;
35
+ private lastUpload?;
36
+ private filePath?;
37
+ private fileHandle?;
38
+ constructor(opts: FileWriterOptions<R>);
39
+ private init;
40
+ /** Update the revision file and create the new file and call onFileStart */
41
+ private rev;
42
+ /** IMPLEMENTATION of the FileWriter interface */
43
+ writeRec(rec: R): Promise<void>;
44
+ private endFile;
45
+ }
@@ -0,0 +1,126 @@
1
+ import { appendFileSync, openSync } from 'fs';
2
+ import { pathExists } from 'fs-aux';
3
+ import { appendFile, mkdir, open, rename } from 'fs/promises';
4
+ import * as Path from "path";
5
+ import { isString } from 'utils-min';
6
+ export class FileWriter {
7
+ constructor(opts) {
8
+ this._init = false;
9
+ this._rev = 0;
10
+ this.count = 0;
11
+ this.nextUpload = null; // null means nothing scheduled
12
+ this.maxCount = opts.maxCount;
13
+ this.maxTime = opts.maxTime;
14
+ this.dir = opts.dir;
15
+ this.fileNameProvider = opts.fileNameProvider ?? defaultFileNameProvider;
16
+ this.fileHeaderProvider = opts.fileHeaderProvider ?? defaultFileHeaderProvider;
17
+ this.onFileCompleted = opts.onFileCompleted;
18
+ this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
19
+ }
20
+ async init() {
21
+ if (!this._init) {
22
+ await mkdir(this.dir, { recursive: true });
23
+ this.rev();
24
+ this._init = true;
25
+ }
26
+ }
27
+ /** Update the revision file and create the new file and call onFileStart */
28
+ async rev() {
29
+ this.count = 0;
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
35
+ const fileName = this.fileNameProvider(this._rev);
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');
49
+ }
50
+ /** IMPLEMENTATION of the FileWriter interface */
51
+ async writeRec(rec) {
52
+ if (!this._init) {
53
+ await this.init();
54
+ }
55
+ const str = this.recordSerializer(rec);
56
+ if (str != null) {
57
+ const strWithNl = str + '\n'; // add the new line
58
+ await appendFile(this.fileHandle, strWithNl);
59
+ }
60
+ // add count
61
+ this.count = this.count + 1;
62
+ // if we are above the count, we upload
63
+ if (this.count > this.maxCount) {
64
+ await this.endFile();
65
+ }
66
+ // if still below the count, but do not have this.nextUpload, schedule one
67
+ else if (this.nextUpload === null) {
68
+ const maxTimeMs = this.maxTime * 1000; // in ms
69
+ const nextUpload = Date.now() + maxTimeMs;
70
+ this.nextUpload = nextUpload;
71
+ setTimeout(async () => {
72
+ // perform only if this.nextUpload match the scheduled nextUpload (otherwise, was already processed and this schedule is outdated)
73
+ if (this.nextUpload === nextUpload) {
74
+ await this.endFile();
75
+ }
76
+ }, maxTimeMs);
77
+ }
78
+ }
79
+ async endFile() {
80
+ const file = this.filePath;
81
+ const fileHandle = this.fileHandle;
82
+ // we rev just before to make sure other logs will happen on new files
83
+ await this.rev();
84
+ // process old file
85
+ try {
86
+ const exists = await pathExists(file);
87
+ if (exists) {
88
+ await fileHandle?.close();
89
+ if (this.onFileCompleted) {
90
+ try {
91
+ await this.onFileCompleted(file);
92
+ }
93
+ catch (ex) {
94
+ console.log(`LOG PROCESSING ERROR - processing file '${file}' caused the following error: ${ex}`);
95
+ }
96
+ }
97
+ }
98
+ else {
99
+ console.log(`LOG PROCESSING REMOVE ERROR - cannot be processed file '${file}' does not exists anymore`);
100
+ }
101
+ }
102
+ // Note: note sure we need this global catch now.
103
+ catch (ex) {
104
+ console.log(`LOG PROCESSING - logger.processLogFile - cannot upload to big query ${file}, ${ex.message}`);
105
+ await rename(file, file + '.error');
106
+ }
107
+ this.count = 0;
108
+ this.lastUpload = Date.now();
109
+ this.nextUpload = null;
110
+ }
111
+ }
112
+ /** default serializer */
113
+ function defaultSerializer(rec) {
114
+ return isString(rec) ? rec : JSON.stringify(rec);
115
+ }
116
+ function defaultFileHeaderProvider() {
117
+ // Return null, meaning, no header
118
+ return null;
119
+ // For CSV, custom onFileStart will need to set the header
120
+ }
121
+ function defaultFileNameProvider(rev) {
122
+ const date = new Date().toISOString().replace(/[T:.]/g, "-").slice(0, -1);
123
+ const revStr = `${rev}`.padStart(5, '0');
124
+ return `log-file-${date}-${revStr}.log`;
125
+ }
126
+ //# sourceMappingURL=log-file-writer.js.map
@@ -0,0 +1 @@
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,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,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,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/dist/log.d.ts CHANGED
@@ -1,53 +1,14 @@
1
- interface LogOptions<R> {
1
+ export interface LogOptions<R> {
2
2
  writers: LogWriter<R>[];
3
3
  }
4
4
  /**
5
- * Base Log class that handle the base log management logic.
5
+ * Base Log class that handle the log management logic and call `writer.writeRec` on the registered logWriters
6
6
  */
7
7
  export declare class BaseLog<R> {
8
- private logWriters;
8
+ #private;
9
9
  constructor(opts: LogOptions<R>);
10
10
  log(rec: R): Promise<void>;
11
11
  }
12
12
  export interface LogWriter<R> {
13
- readonly name: string;
14
13
  writeRec?(rec: R): Promise<void>;
15
14
  }
16
- /** processing file, if return true, then file will be assumed to be full processed, and will be deleted */
17
- export declare type FileProcessor = (file: string) => Promise<boolean>;
18
- /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
19
- export declare type RecordSerializer<R> = (rec: R) => string | null;
20
- interface FileLogWriterOptions<R> {
21
- /** name of the logWriter, will be used as prefix */
22
- name: string;
23
- /** Local directory in which the logs files will be saved */
24
- dir: string;
25
- /** maxCount of record before file is uploaded to destination */
26
- maxCount: number;
27
- /** max time (in ms) before file is uploaded to destination (which ever comes first with maxCount) */
28
- maxTime: number;
29
- fileProcessor?: FileProcessor;
30
- recordSerializer?: RecordSerializer<R>;
31
- }
32
- export declare class FileLogWriter<R> implements LogWriter<R> {
33
- readonly name: string;
34
- private dir;
35
- private maxCount;
36
- private maxTime;
37
- private fileProcessor?;
38
- private recordSerializer;
39
- private _init;
40
- private _rev;
41
- private count;
42
- private nextUpload;
43
- private lastUpload?;
44
- private file?;
45
- constructor(opts: FileLogWriterOptions<R>);
46
- private init;
47
- /** Update the revision file */
48
- private rev;
49
- /** IMPLEMENTATION of the FileWriter interface */
50
- writeRec(rec: R): Promise<void>;
51
- private endFile;
52
- }
53
- export {};
package/dist/log.js CHANGED
@@ -1,119 +1,37 @@
1
- import * as Path from 'path';
2
- import { isString } from 'utils-min';
3
- import { glob, saferRemove } from './fs.js';
4
- const { pathExists, mkdirs, appendFile, rename } = (await import('fs-extra')).default;
1
+ // const { pathExists, mkdirs, appendFile, rename } = (await import('fs-extra')).default;
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _BaseLog_logWriters;
5
14
  /**
6
- * Base Log class that handle the base log management logic.
15
+ * Base Log class that handle the log management logic and call `writer.writeRec` on the registered logWriters
7
16
  */
8
17
  export class BaseLog {
9
18
  constructor(opts) {
10
- this.logWriters = [];
11
- this.logWriters = [...opts.writers];
19
+ _BaseLog_logWriters.set(this, []);
20
+ __classPrivateFieldSet(this, _BaseLog_logWriters, [...opts.writers], "f");
12
21
  }
13
22
  async log(rec) {
14
- //
15
- for (const writer of this.logWriters) {
23
+ for (const writer of __classPrivateFieldGet(this, _BaseLog_logWriters, "f")) {
16
24
  if (writer.writeRec) {
17
25
  try {
18
26
  await writer.writeRec(rec);
19
27
  }
20
28
  catch (ex) {
21
29
  // here log console.log, no choise
22
- console.log(`LOG ERROR - Log exception when writeRec on logWriter ${writer.name}. ${ex}`);
30
+ console.log(`ERROR - BACKLIB - Log exception when calling writeRec on logWriter ${writer}. ${ex}`);
23
31
  }
24
32
  }
25
33
  }
26
34
  }
27
35
  }
28
- export class FileLogWriter {
29
- constructor(opts) {
30
- this._init = false;
31
- this._rev = 0;
32
- this.count = 0;
33
- this.nextUpload = null; // null means nothing scheduled
34
- this.name = opts.name;
35
- this.maxCount = opts.maxCount;
36
- this.maxTime = opts.maxTime;
37
- this.dir = opts.dir;
38
- this.fileProcessor = opts.fileProcessor;
39
- this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
40
- }
41
- async init() {
42
- if (!this._init) {
43
- await mkdirs(this.dir);
44
- // delete the logs dir if exit
45
- const oldLogFiles = await glob(this.dir + `${this.name}*.log`);
46
- await saferRemove(oldLogFiles);
47
- console.log('Deleted old log files', oldLogFiles);
48
- this.rev();
49
- this._init = true;
50
- }
51
- }
52
- /** Update the revision file */
53
- rev() {
54
- this.count = 0;
55
- this._rev = this._rev + 1;
56
- const suffix = `${this._rev}`.padStart(5, '0');
57
- this.file = Path.join(this.dir, `${this.name}-${suffix}.log`);
58
- }
59
- /** IMPLEMENTATION of the FileWriter interface */
60
- async writeRec(rec) {
61
- if (!this._init) {
62
- await this.init();
63
- }
64
- // TODO: Need to move this outside of the generic log implementation (we do this because bigquery expect info to be string, since it can be dynamic)
65
- // NOTE: In fact, this whole file write and upload, should be part of a FileLogWriter, and we just treat it as above (perhaps in the BigQueryLogWriter extends FileLogWriter)
66
- const str = this.recordSerializer(rec);
67
- if (str != null) {
68
- await appendFile(this.file, str);
69
- }
70
- // add count
71
- this.count = this.count + 1;
72
- // if we are above the count, we upload
73
- if (this.count > this.maxCount) {
74
- await this.endFile();
75
- }
76
- // if still below the count, but do not have this.nextUpload, schedule one
77
- else if (this.nextUpload === null) {
78
- const maxTimeMs = this.maxTime * 1000; // in ms
79
- const nextUpload = Date.now() + maxTimeMs;
80
- this.nextUpload = nextUpload;
81
- setTimeout(async () => {
82
- // perform only if this.nextUpload match the scheduled nextUpload (otherwise, was already processed and this schedule is outdated)
83
- if (this.nextUpload === nextUpload) {
84
- await this.endFile();
85
- }
86
- }, maxTimeMs);
87
- }
88
- }
89
- async endFile() {
90
- const file = this.file;
91
- // we rev just before to make sure other logs will happen on new files
92
- this.rev();
93
- try {
94
- const exists = await pathExists(file);
95
- if (exists) {
96
- if (this.fileProcessor) {
97
- await this.fileProcessor(file);
98
- }
99
- await saferRemove(file);
100
- }
101
- else {
102
- console.log(`CODE ERROR - can't upload to big query ${file} does not exists`);
103
- }
104
- }
105
- catch (ex) {
106
- console.log(`ERROR - logger.processLogFile - cannot upload to big query ${file}, ${ex.message}`);
107
- await rename(file, file + '.error');
108
- }
109
- this.count = 0;
110
- this.lastUpload = Date.now();
111
- this.nextUpload = null;
112
- }
113
- }
114
- /** default serializer */
115
- function defaultSerializer(rec) {
116
- return isString(rec) ? rec : JSON.stringify(rec);
117
- }
118
- //#endregion ---------- /FileLogWriter ----------
36
+ _BaseLog_logWriters = new WeakMap();
119
37
  //# sourceMappingURL=log.js.map
package/dist/log.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AACA,yFAAyF;;;;;;;;;;;;;AAQzF;;GAEG;AACH,MAAM,OAAO,OAAO;IAGnB,YAAY,IAAmB;QAF/B,8BAA8B,EAAE,EAAC;QAGhC,uBAAA,IAAI,uBAAe,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAA,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAM;QAEf,KAAK,MAAM,MAAM,IAAI,uBAAA,IAAI,2BAAY,EAAE;YACtC,IAAI,MAAM,CAAC,QAAQ,EAAE;gBACpB,IAAI;oBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;iBAC3B;gBAAC,OAAO,EAAE,EAAE;oBACZ,kCAAkC;oBAClC,OAAO,CAAC,GAAG,CAAC,sEAAsE,MAAM,KAAK,EAAE,EAAE,CAAC,CAAC;iBACnG;aACD;SACD;IAEF,CAAC;CAED"}
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "backlib",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.2-SNAPSHOT.1",
5
5
  "description": "Minimalist library for backend services",
6
6
  "main": "dist/index.js",
7
- "typings": "dist/index.d.ts",
8
7
  "engines": {
9
- "node": ">=14.4"
8
+ "node": ">=16.13"
10
9
  },
11
10
  "repository": {
12
11
  "type": "git",
@@ -19,12 +18,11 @@
19
18
  "author": "jeremy.chone@gmail.com",
20
19
  "license": "MIT",
21
20
  "dependencies": {
22
- "fast-glob": "^3.2.11",
23
- "fs-extra": "^10.0.0",
21
+ "fs-aux": "^0.1.0",
24
22
  "utils-min": "^0.2.0"
25
23
  },
26
24
  "devDependencies": {
27
- "@types/fs-extra": "^9.0.13",
25
+ "@types/node": "^17.0.10",
28
26
  "rimraf": "^3.0.2",
29
27
  "typescript": "^4.5.5"
30
28
  },
package/src/index.ts CHANGED
@@ -1,5 +1,19 @@
1
- export * from './decorator-leaf-tracer.js';
2
- export * from './fs.js';
3
- export * from './log.js';
4
- export * from './utils.js';
1
+ // Export decorator constructs
2
+ export { newLeafTracer } from './decorator-leaf-tracer.js';
3
+ // Export log-file-writer constructs
4
+ export { FileWriter } from './log-file-writer.js';
5
+ export type { FileNameProvider, FileWriterOptions, OnFileCompleted, RecordSerializer } from './log-file-writer.js';
6
+ // Export log constructs
7
+ export { BaseLog } from './log.js';
8
+ export type { LogOptions, LogWriter } from './log.js';
9
+ // Export utils constructs
10
+ export { prompt } from './utils.js';
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
5
19
 
@@ -0,0 +1,190 @@
1
+ import { appendFileSync, openSync } from 'fs';
2
+ import { pathExists } from 'fs-aux';
3
+ import { appendFile, FileHandle, mkdir, open, rename } from 'fs/promises';
4
+ import * as Path from "path";
5
+ import { isString } from 'utils-min';
6
+ import { LogWriter } from './index.js';
7
+
8
+ export type FileNameProvider = (rev: number) => string;
9
+ export type FileHeaderProvider = () => string | null; // must be sync
10
+ export type OnFileCompleted = (file: string) => Promise<void>;
11
+
12
+ /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
13
+ export type RecordSerializer<R> = (rec: R) => string | null;
14
+
15
+ export interface FileWriterOptions<R> {
16
+ /** Local directory in which the logs files will be saved */
17
+ dir: string;
18
+ /** maxCount of record before file is uploaded to destination */
19
+ maxCount: number;
20
+ /** max time (in seconds) before file is uploaded to destination (which ever comes first with maxCount) */
21
+ maxTime: number;
22
+
23
+ /** Optional fileName generator for the new log file name (MUST BE UNIQUE for this dir) */
24
+ fileNameProvider?: FileNameProvider,
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) */
28
+ recordSerializer?: RecordSerializer<R>;
29
+ /** Called when a log file is completed (i.e., new entries will go to another file) */
30
+ onFileCompleted?: OnFileCompleted;
31
+
32
+ }
33
+
34
+ export class FileWriter<R> implements LogWriter<R> {
35
+ private dir: string;
36
+ private maxCount: number;
37
+ private maxTime: number;
38
+
39
+ private fileNameProvider: FileNameProvider;
40
+ private fileHeaderProvider: FileHeaderProvider;
41
+ private recordSerializer: RecordSerializer<R>;
42
+ private onFileCompleted?: OnFileCompleted;
43
+
44
+ private _init = false;
45
+ private _rev = 0;
46
+ private count = 0;
47
+ private nextUpload: number | null = null; // null means nothing scheduled
48
+ private lastUpload?: number;
49
+
50
+ private filePath?: string;
51
+ private fileHandle?: FileHandle;
52
+
53
+ constructor(opts: FileWriterOptions<R>) {
54
+ this.maxCount = opts.maxCount;
55
+ this.maxTime = opts.maxTime;
56
+ this.dir = opts.dir;
57
+ this.fileNameProvider = opts.fileNameProvider ?? defaultFileNameProvider;
58
+ this.fileHeaderProvider = opts.fileHeaderProvider ?? defaultFileHeaderProvider;
59
+ this.onFileCompleted = opts.onFileCompleted;
60
+ this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
61
+ }
62
+
63
+ private async init() {
64
+ if (!this._init) {
65
+ await mkdir(this.dir, { recursive: true });
66
+ this.rev();
67
+ this._init = true;
68
+ }
69
+ }
70
+
71
+ /** Update the revision file and create the new file and call onFileStart */
72
+ private async rev() {
73
+ this.count = 0;
74
+ this._rev = this._rev + 1;
75
+
76
+ // make sure to reset the file info
77
+ this.filePath = undefined;
78
+ this.fileHandle = undefined;
79
+
80
+ // -- CREATE the new file
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
+ }
93
+
94
+ // -- CREATE the file handler
95
+ this.fileHandle = await open(this.filePath, 'a');
96
+ }
97
+
98
+
99
+ /** IMPLEMENTATION of the FileWriter interface */
100
+ async writeRec(rec: R) {
101
+
102
+ if (!this._init) {
103
+ await this.init();
104
+ }
105
+
106
+ const str = this.recordSerializer(rec);
107
+ if (str != null) {
108
+ const strWithNl = str + '\n'; // add the new line
109
+ await appendFile(this.fileHandle!, strWithNl);
110
+ }
111
+
112
+ // add count
113
+ this.count = this.count + 1;
114
+
115
+ // if we are above the count, we upload
116
+ if (this.count > this.maxCount) {
117
+ await this.endFile();
118
+ }
119
+ // if still below the count, but do not have this.nextUpload, schedule one
120
+ else if (this.nextUpload === null) {
121
+ const maxTimeMs = this.maxTime * 1000; // in ms
122
+
123
+ const nextUpload = Date.now() + maxTimeMs;
124
+ this.nextUpload = nextUpload;
125
+
126
+ setTimeout(async () => {
127
+ // perform only if this.nextUpload match the scheduled nextUpload (otherwise, was already processed and this schedule is outdated)
128
+ if (this.nextUpload === nextUpload) {
129
+ await this.endFile();
130
+ }
131
+ }, maxTimeMs);
132
+ }
133
+
134
+ }
135
+
136
+ private async endFile() {
137
+ const file = this.filePath!;
138
+ const fileHandle = this.fileHandle;
139
+
140
+ // we rev just before to make sure other logs will happen on new files
141
+ await this.rev();
142
+
143
+ // process old file
144
+ try {
145
+ const exists = await pathExists(file);
146
+ if (exists) {
147
+ await fileHandle?.close();
148
+ if (this.onFileCompleted) {
149
+ try {
150
+ await this.onFileCompleted(file);
151
+ } catch (ex: any) {
152
+ console.log(`LOG PROCESSING ERROR - processing file '${file}' caused the following error: ${ex}`);
153
+ }
154
+ }
155
+ } else {
156
+ console.log(`LOG PROCESSING REMOVE ERROR - cannot be processed file '${file}' does not exists anymore`);
157
+ }
158
+
159
+ }
160
+ // Note: note sure we need this global catch now.
161
+ catch (ex: any) {
162
+ console.log(`LOG PROCESSING - logger.processLogFile - cannot upload to big query ${file}, ${ex.message}`);
163
+ await rename(file, file + '.error');
164
+ }
165
+
166
+ this.count = 0;
167
+ this.lastUpload = Date.now();
168
+ this.nextUpload = null;
169
+ }
170
+
171
+ }
172
+
173
+ /** default serializer */
174
+
175
+ function defaultSerializer<R>(rec: R): string {
176
+ return isString(rec) ? rec : JSON.stringify(rec);
177
+ }
178
+
179
+ function defaultFileHeaderProvider(): string | null {
180
+ // Return null, meaning, no header
181
+ return null;
182
+
183
+ // For CSV, custom onFileStart will need to set the header
184
+ }
185
+
186
+ function defaultFileNameProvider(rev: number): string {
187
+ const date = new Date().toISOString().replace(/[T:.]/g, "-").slice(0, -1);
188
+ const revStr = `${rev}`.padStart(5, '0');
189
+ return `log-file-${date}-${revStr}.log`;
190
+ }
package/src/log.ts CHANGED
@@ -1,34 +1,31 @@
1
- import * as Path from 'path';
2
- import { isString } from 'utils-min';
3
- import { glob, saferRemove } from './fs.js';
4
- const { pathExists, mkdirs, appendFile, rename } = (await import('fs-extra')).default;
1
+
2
+ // const { pathExists, mkdirs, appendFile, rename } = (await import('fs-extra')).default;
5
3
 
6
4
 
7
5
  //#region ---------- BaseLog ----------
8
- interface LogOptions<R> {
6
+ export interface LogOptions<R> {
9
7
  writers: LogWriter<R>[]
10
8
  }
11
9
 
12
10
  /**
13
- * Base Log class that handle the base log management logic.
11
+ * Base Log class that handle the log management logic and call `writer.writeRec` on the registered logWriters
14
12
  */
15
13
  export class BaseLog<R> {
16
- private logWriters: LogWriter<R>[] = [];
14
+ #logWriters: LogWriter<R>[] = [];
17
15
 
18
16
  constructor(opts: LogOptions<R>) {
19
- this.logWriters = [...opts.writers];
17
+ this.#logWriters = [...opts.writers];
20
18
  }
21
19
 
22
20
  async log(rec: R) {
23
21
 
24
- //
25
- for (const writer of this.logWriters) {
22
+ for (const writer of this.#logWriters) {
26
23
  if (writer.writeRec) {
27
24
  try {
28
25
  await writer.writeRec(rec);
29
26
  } catch (ex) {
30
27
  // here log console.log, no choise
31
- console.log(`LOG ERROR - Log exception when writeRec on logWriter ${writer.name}. ${ex}`);
28
+ console.log(`ERROR - BACKLIB - Log exception when calling writeRec on logWriter ${writer}. ${ex}`);
32
29
  }
33
30
  }
34
31
  }
@@ -39,160 +36,6 @@ export class BaseLog<R> {
39
36
  //#endregion ---------- /BaseLog ----------
40
37
 
41
38
  export interface LogWriter<R> {
42
- readonly name: string;
43
39
  writeRec?(rec: R): Promise<void>
44
40
  }
45
41
 
46
-
47
- //#region ---------- FileLogWriter ----------
48
- /** processing file, if return true, then file will be assumed to be full processed, and will be deleted */
49
- export type FileProcessor = (file: string) => Promise<boolean>;
50
-
51
- /** Record serializer to string, which will be appended to the file. If null, record will be skipped */
52
- export type RecordSerializer<R> = (rec: R) => string | null;
53
-
54
- interface FileLogWriterOptions<R> {
55
- /** name of the logWriter, will be used as prefix */
56
- name: string;
57
- /** Local directory in which the logs files will be saved */
58
- dir: string;
59
- /** maxCount of record before file is uploaded to destination */
60
- maxCount: number;
61
- /** max time (in ms) before file is uploaded to destination (which ever comes first with maxCount) */
62
- maxTime: number;
63
-
64
- /* Mehod to process the file (.e.g., upload to bucket, bigquery, ...) */
65
- fileProcessor?: FileProcessor;
66
-
67
- /* Optional recordSerializer to file. By default, JSON.serializer() (new line json) */
68
- recordSerializer?: RecordSerializer<R>;
69
- }
70
-
71
-
72
-
73
- export class FileLogWriter<R> implements LogWriter<R> {
74
-
75
- readonly name: string;
76
- private dir: string;
77
- private maxCount: number;
78
- private maxTime: number;
79
- private fileProcessor?: FileProcessor;
80
- private recordSerializer: RecordSerializer<R>;
81
-
82
- private _init = false;
83
- private _rev = 0;
84
- private count = 0;
85
- private nextUpload: number | null = null; // null means nothing scheduled
86
- private lastUpload?: number;
87
-
88
- private file?: string;
89
-
90
-
91
- constructor(opts: FileLogWriterOptions<R>) {
92
- this.name = opts.name;
93
- this.maxCount = opts.maxCount;
94
- this.maxTime = opts.maxTime;
95
- this.dir = opts.dir;
96
- this.fileProcessor = opts.fileProcessor;
97
- this.recordSerializer = opts.recordSerializer ?? defaultSerializer;
98
- }
99
-
100
- private async init() {
101
- if (!this._init) {
102
- await mkdirs(this.dir);
103
-
104
- // delete the logs dir if exit
105
- const oldLogFiles = await glob(this.dir + `${this.name}*.log`);
106
- await saferRemove(oldLogFiles);
107
- console.log('Deleted old log files', oldLogFiles);
108
-
109
- this.rev();
110
-
111
- this._init = true;
112
- }
113
-
114
- }
115
-
116
- /** Update the revision file */
117
- private rev() {
118
- this.count = 0;
119
- this._rev = this._rev + 1;
120
- const suffix = `${this._rev}`.padStart(5, '0');
121
- this.file = Path.join(this.dir, `${this.name}-${suffix}.log`)
122
- }
123
-
124
-
125
- /** IMPLEMENTATION of the FileWriter interface */
126
- async writeRec(rec: R) {
127
-
128
- if (!this._init) {
129
- await this.init();
130
- }
131
-
132
- // TODO: Need to move this outside of the generic log implementation (we do this because bigquery expect info to be string, since it can be dynamic)
133
- // NOTE: In fact, this whole file write and upload, should be part of a FileLogWriter, and we just treat it as above (perhaps in the BigQueryLogWriter extends FileLogWriter)
134
- const str = this.recordSerializer(rec);
135
- if (str != null) {
136
- await appendFile(this.file!, str);
137
- }
138
-
139
- // add count
140
- this.count = this.count + 1;
141
-
142
- // if we are above the count, we upload
143
- if (this.count > this.maxCount) {
144
- await this.endFile();
145
- }
146
- // if still below the count, but do not have this.nextUpload, schedule one
147
- else if (this.nextUpload === null) {
148
- const maxTimeMs = this.maxTime * 1000; // in ms
149
-
150
- const nextUpload = Date.now() + maxTimeMs;
151
- this.nextUpload = nextUpload;
152
-
153
- setTimeout(async () => {
154
- // perform only if this.nextUpload match the scheduled nextUpload (otherwise, was already processed and this schedule is outdated)
155
- if (this.nextUpload === nextUpload) {
156
- await this.endFile();
157
- }
158
- }, maxTimeMs);
159
- }
160
-
161
- }
162
-
163
- private async endFile() {
164
- const file = this.file!;
165
- // we rev just before to make sure other logs will happen on new files
166
- this.rev();
167
-
168
- try {
169
- const exists = await pathExists(file);
170
- if (exists) {
171
- if (this.fileProcessor) {
172
- await this.fileProcessor(file);
173
- }
174
- await saferRemove(file);
175
- } else {
176
- console.log(`CODE ERROR - can't upload to big query ${file} does not exists`);
177
- }
178
-
179
- } catch (ex: any) {
180
- console.log(`ERROR - logger.processLogFile - cannot upload to big query ${file}, ${ex.message}`);
181
- await rename(file, file + '.error');
182
- }
183
-
184
- this.count = 0;
185
- this.lastUpload = Date.now();
186
- this.nextUpload = null;
187
- }
188
-
189
- }
190
-
191
- /** default serializer */
192
-
193
- function defaultSerializer<R>(rec: R): string {
194
- return isString(rec) ? rec : JSON.stringify(rec);
195
- }
196
-
197
- //#endregion ---------- /FileLogWriter ----------
198
-
package/dist/fs.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import { Options } from 'fast-glob';
2
- /**
3
- * Simplified and sorted glob function (using fast-glob) for one or more pattern from current directory or a optional cwd one.
4
- *
5
- * Note 1: The result will be sorted by natural directory/subdir/filename order (as a would a recursive walk)
6
- * Note 2: When `cwd` in options, it is added to the file path i.e. `pathJoin(cwd, path)`
7
- *
8
- * @returns always sorted result return Promise<string[]>
9
- */
10
- export declare function glob(pattern: string | string[], cwdOrFastGlobOptions?: string | Options): Promise<string[]>;
11
- /** Remove one or more files. Resolved the number of names removed */
12
- export declare function saferRemove(names: string | string[], cwd?: string): Promise<string[]>;
package/dist/fs.js DELETED
@@ -1,70 +0,0 @@
1
- import FastGlob from 'fast-glob';
2
- import { join as pathJoin, resolve as pathResolve } from 'path';
3
- import { asArray } from 'utils-min';
4
- const { pathExists, remove } = (await import('fs-extra')).default;
5
- /**
6
- * Simplified and sorted glob function (using fast-glob) for one or more pattern from current directory or a optional cwd one.
7
- *
8
- * Note 1: The result will be sorted by natural directory/subdir/filename order (as a would a recursive walk)
9
- * Note 2: When `cwd` in options, it is added to the file path i.e. `pathJoin(cwd, path)`
10
- *
11
- * @returns always sorted result return Promise<string[]>
12
- */
13
- export async function glob(pattern, cwdOrFastGlobOptions) {
14
- let opts = undefined;
15
- if (cwdOrFastGlobOptions != null) {
16
- opts = (typeof cwdOrFastGlobOptions === 'string') ? { cwd: cwdOrFastGlobOptions } : cwdOrFastGlobOptions;
17
- }
18
- const result = await FastGlob(pattern, opts);
19
- const cwd = (opts) ? opts.cwd : undefined;
20
- const list = result.map(path => {
21
- return (cwd) ? pathJoin(cwd, path) : path;
22
- });
23
- return list.sort(globCompare);
24
- }
25
- /** Remove one or more files. Resolved the number of names removed */
26
- export async function saferRemove(names, cwd) {
27
- const baseDir = (cwd) ? pathResolve(cwd) : pathResolve('./');
28
- let removedNames = [];
29
- for (const name of asArray(names)) {
30
- const fullPath = pathJoin(baseDir, name);
31
- if (!fullPath.startsWith(baseDir)) {
32
- throw new Error(`Path to be removed does not look safe (nothing done): ${fullPath}\n\tCause: Does not belong to ${baseDir}`);
33
- }
34
- const exists = await pathExists(fullPath);
35
- if (exists) {
36
- await remove(fullPath);
37
- removedNames.push(name);
38
- }
39
- }
40
- return removedNames;
41
- }
42
- //#region ---------- Utils ----------
43
- function globCompare(a, b) {
44
- const aPathIdxs = pathIndexes(a);
45
- const bPathIdxs = pathIndexes(b);
46
- const minIdx = Math.min(aPathIdxs.length, bPathIdxs.length) - 1;
47
- const aMinPath = a.substring(0, aPathIdxs[minIdx]);
48
- const bMinPath = b.substring(0, bPathIdxs[minIdx]);
49
- // if the common path is the same, and the path depth is different, then, the shortest one come first;
50
- if ((aMinPath === bMinPath) && (aPathIdxs.length !== bPathIdxs.length)) {
51
- return (aPathIdxs.length < bPathIdxs.length) ? -1 : 1;
52
- }
53
- // otherwise, we do a normal compare
54
- return (a < b) ? -1 : 1;
55
- }
56
- function pathIndexes(fullPath) {
57
- const idxs = [];
58
- const l = fullPath.length;
59
- for (let i = 0; i < l; i++) {
60
- if (fullPath[i] === '/') {
61
- idxs.push(i);
62
- }
63
- }
64
- return idxs;
65
- }
66
- // function asArray(names: string | string[]) {
67
- // return (names instanceof Array) ? names : [names];
68
- // }
69
- //#endregion ---------- /Utils ----------
70
- //# sourceMappingURL=fs.js.map
package/dist/fs.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
package/src/fs.ts DELETED
@@ -1,84 +0,0 @@
1
- import FastGlob, { Options } from 'fast-glob';
2
- import { join as pathJoin, resolve as pathResolve } from 'path';
3
- import { asArray } from 'utils-min';
4
- const { pathExists, remove } = (await import('fs-extra')).default;
5
-
6
- /**
7
- * Simplified and sorted glob function (using fast-glob) for one or more pattern from current directory or a optional cwd one.
8
- *
9
- * Note 1: The result will be sorted by natural directory/subdir/filename order (as a would a recursive walk)
10
- * Note 2: When `cwd` in options, it is added to the file path i.e. `pathJoin(cwd, path)`
11
- *
12
- * @returns always sorted result return Promise<string[]>
13
- */
14
- export async function glob(pattern: string | string[], cwdOrFastGlobOptions?: string | Options): Promise<string[]> {
15
- let opts: Options | undefined = undefined;
16
-
17
- if (cwdOrFastGlobOptions != null) {
18
- opts = (typeof cwdOrFastGlobOptions === 'string') ? { cwd: cwdOrFastGlobOptions } : cwdOrFastGlobOptions;
19
- }
20
-
21
- const result = await FastGlob(pattern, opts);
22
- const cwd = (opts) ? opts.cwd : undefined;
23
- const list = result.map(path => {
24
- return (cwd) ? pathJoin(cwd, path) : path;
25
- });
26
- return list.sort(globCompare);
27
- }
28
-
29
- /** Remove one or more files. Resolved the number of names removed */
30
- export async function saferRemove(names: string | string[], cwd?: string): Promise<string[]> {
31
- const baseDir = (cwd) ? pathResolve(cwd) : pathResolve('./');
32
- let removedNames: string[] = [];
33
-
34
- for (const name of asArray(names)) {
35
- const fullPath = pathJoin(baseDir, name);
36
- if (!fullPath.startsWith(baseDir)) {
37
- throw new Error(`Path to be removed does not look safe (nothing done): ${fullPath}\n\tCause: Does not belong to ${baseDir}`);
38
- }
39
- const exists = await pathExists(fullPath);
40
- if (exists) {
41
- await remove(fullPath);
42
- removedNames.push(name);
43
- }
44
- }
45
- return removedNames;
46
- }
47
-
48
-
49
- //#region ---------- Utils ----------
50
- function globCompare(a: string, b: string) {
51
- const aPathIdxs = pathIndexes(a);
52
- const bPathIdxs = pathIndexes(b);
53
- const minIdx = Math.min(aPathIdxs.length, bPathIdxs.length) - 1;
54
- const aMinPath = a.substring(0, aPathIdxs[minIdx]);
55
- const bMinPath = b.substring(0, bPathIdxs[minIdx]);
56
-
57
- // if the common path is the same, and the path depth is different, then, the shortest one come first;
58
- if ((aMinPath === bMinPath) && (aPathIdxs.length !== bPathIdxs.length)) {
59
- return (aPathIdxs.length < bPathIdxs.length) ? -1 : 1;
60
- }
61
-
62
- // otherwise, we do a normal compare
63
- return (a < b) ? -1 : 1;
64
-
65
-
66
- }
67
-
68
- function pathIndexes(fullPath: string): number[] {
69
- const idxs: number[] = [];
70
-
71
- const l = fullPath.length;
72
- for (let i = 0; i < l; i++) {
73
- if (fullPath[i] === '/') {
74
- idxs.push(i);
75
- }
76
- }
77
-
78
- return idxs;
79
- }
80
-
81
- // function asArray(names: string | string[]) {
82
- // return (names instanceof Array) ? names : [names];
83
- // }
84
- //#endregion ---------- /Utils ----------