@brianbuie/node-kit 0.15.1 → 0.16.0

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/src/Log.ts CHANGED
@@ -1,126 +1,86 @@
1
- import { inspect } from 'node:util';
2
- import { isObjectLike } from 'lodash-es';
3
- import chalk, { type ChalkInstance } from 'chalk';
4
- import { snapshot } from './snapshot.ts';
5
- import { Format } from './Format.ts';
1
+ import { merge } from 'lodash-es';
2
+ import { default as pino, type Logger } from 'pino';
6
3
 
7
- // https://docs.cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
8
- type Severity = 'DEFAULT' | 'DEBUG' | 'INFO' | 'NOTICE' | 'WARNING' | 'ERROR' | 'CRITICAL' | 'ALERT' | 'EMERGENCY';
4
+ export type LogDetails = Record<string, unknown>;
9
5
 
10
- type Options = {
11
- severity: Severity;
12
- color: ChalkInstance;
13
- };
6
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
14
7
 
15
- type Entry = {
16
- message?: string;
17
- severity: Severity;
18
- stack?: string;
19
- details?: unknown[];
8
+ export type LogOptions = pino.LoggerOptions & {
9
+ environment?: string;
20
10
  };
21
11
 
12
+ /**
13
+ * Wrapper for [pino](https://github.com/pinojs/pino)
14
+ * Levels: fatal, error, warn, info, debug, trace
15
+ * Use `LOG_LEVL=info` to limit what's printed to console
16
+ * Use `Log.configure` to customize the pino instance
17
+ */
22
18
  export class Log {
23
- static getStack() {
24
- const details = { stack: '' };
25
- // replaces details.stack with current stack trace, excluding this Log.getStack call
26
- Error.captureStackTrace(details, Log.getStack);
27
- // remove 'Error' on first line
28
- return details.stack
29
- .split('\n')
30
- .map(l => l.trim())
31
- .filter(l => l !== 'Error');
32
- }
19
+ static #logger?: Logger;
20
+ static #options: LogOptions;
33
21
 
34
- /**
35
- * Gcloud parses JSON in stdout
36
- */
37
- static #toGcloud(entry: Entry) {
38
- const details = entry.details?.length === 1 ? entry.details[0] : entry.details;
39
- const output = { ...entry, details, stack: entry.stack || this.getStack() };
40
- console.log(JSON.stringify(snapshot(output)));
22
+ static createLogger(): Logger {
23
+ const isProduction = this.#options?.environment === 'production' || process.env.NODE_ENV === 'production';
24
+ const defaultOptions: LogOptions = {
25
+ level: process.env.LOG_LEVEL ?? (isProduction ? 'info' : 'debug'),
26
+ timestamp: pino.stdTimeFunctions.isoTime,
27
+ formatters: {
28
+ level(label: string) {
29
+ return { level: label };
30
+ },
31
+ },
32
+ transport: isProduction
33
+ ? undefined
34
+ : {
35
+ target: 'pino-pretty',
36
+ options: {
37
+ colorize: true,
38
+ singleLine: true,
39
+ ignore: 'pid,hostname',
40
+ },
41
+ },
42
+ };
43
+ return pino(merge(defaultOptions, this.#options));
41
44
  }
42
45
 
43
- /**
44
- * Includes colors and better inspection for logging during dev
45
- */
46
- static #toConsole(entry: Entry, color: ChalkInstance) {
47
- if (entry.message) console.log(color(`${Format.date('h:m:s')} [${entry.severity}] ${entry.message}`));
48
- entry.details?.forEach(detail => {
49
- console.log(inspect(detail, { depth: 10, breakLength: 100, compact: true, colors: true }));
50
- });
46
+ static configure(options: LogOptions = {}): void {
47
+ this.#options = options;
48
+ this.#logger = this.createLogger();
51
49
  }
52
50
 
53
- static #log({ severity, color }: Options, ...input: unknown[]) {
54
- const { message, details } = this.prepare(...input);
55
- const entry: Entry = { message, severity, details };
56
- // https://cloud.google.com/run/docs/container-contract#env-vars
57
- const isGcloud = process.env.K_SERVICE !== undefined || process.env.CLOUD_RUN_JOB !== undefined;
58
- if (isGcloud) {
59
- this.#toGcloud(entry);
60
- } else {
61
- this.#toConsole(entry, color);
62
- }
63
- return entry;
51
+ static #getLogger(): Logger {
52
+ return (this.#logger ??= this.createLogger());
64
53
  }
65
54
 
66
- /**
67
- * Handle first argument being a string or an object with a 'message' prop
68
- */
69
- static prepare(...input: unknown[]): { message?: string; details: unknown[] } {
70
- let [firstArg, ...rest] = input;
71
- // First argument is a string, use that as the message
72
- if (typeof firstArg === 'string') {
73
- return { message: firstArg, details: rest };
74
- }
75
- // First argument is an object with a `message` property
76
- // @ts-ignore
77
- if (isObjectLike(firstArg) && typeof firstArg['message'] === 'string') {
78
- const { message, ...firstDetails } = firstArg as { message: string };
79
- return { message, details: [firstDetails, ...rest] };
55
+ static #write(level: LogLevel, message: string, details?: LogDetails): void {
56
+ if (details === undefined) {
57
+ Log.#getLogger()[level](message);
58
+ } else {
59
+ Log.#getLogger()[level](details, message);
80
60
  }
81
- // No message found, log all args as details
82
- return { details: input };
83
61
  }
84
62
 
85
- /**
86
- * Events that require action or attention immediately
87
- */
88
- static alert(...input: unknown[]) {
89
- return this.#log({ severity: 'ALERT', color: chalk.bgRed }, ...input);
63
+ static trace(message: string, details?: LogDetails): void {
64
+ Log.#write('trace', message, details);
90
65
  }
91
66
 
92
- /**
93
- * Events that cause problems
94
- */
95
- static error(...input: unknown[]) {
96
- return this.#log({ severity: 'ERROR', color: chalk.red }, ...input);
67
+ static debug(message: string, details?: LogDetails): void {
68
+ Log.#write('debug', message, details);
97
69
  }
98
70
 
99
- /**
100
- * Events that might cause problems
101
- */
102
- static warn(...input: unknown[]) {
103
- return this.#log({ severity: 'WARNING', color: chalk.yellow }, ...input);
71
+ static info(message: string, details?: LogDetails): void {
72
+ Log.#write('info', message, details);
104
73
  }
105
74
 
106
- /**
107
- * Normal but significant events, such as start up, shut down, or a configuration change
108
- */
109
- static notice(...input: unknown[]) {
110
- return this.#log({ severity: 'NOTICE', color: chalk.cyan }, ...input);
75
+ static warn(message: string, details?: LogDetails): void {
76
+ Log.#write('warn', message, details);
111
77
  }
112
78
 
113
- /**
114
- * Routine information, such as ongoing status or performance
115
- */
116
- static info(...input: unknown[]) {
117
- return this.#log({ severity: 'INFO', color: chalk.white }, ...input);
79
+ static error(message: string, details?: LogDetails): void {
80
+ Log.#write('error', message, details);
118
81
  }
119
82
 
120
- /**
121
- * Debug or trace information
122
- */
123
- static debug(...input: unknown[]) {
124
- return this.#log({ severity: 'DEBUG', color: chalk.gray }, ...input);
83
+ static fatal(message: string, details?: LogDetails): void {
84
+ Log.#write('fatal', message, details);
125
85
  }
126
86
  }
package/src/TypeWriter.ts CHANGED
@@ -2,12 +2,20 @@ import * as fs from 'node:fs';
2
2
  import { merge } from 'lodash-es';
3
3
  import * as qt from 'quicktype-core';
4
4
 
5
+ /**
6
+ * Wrapper for [quicktype-core](https://github.com/glideapps/quicktype)
7
+ * @example
8
+ * const group = new TypeWriter('Group');
9
+ * await types.addMember('Thing', [{ a: 1 }, { a: 2, b: 1 }]);
10
+ * await types.toFile();
11
+ * // type def for `Thing` saved in `types/Group.types.ts`
12
+ */
5
13
  export class TypeWriter {
6
- moduleName;
14
+ moduleName: string;
7
15
  input = qt.jsonInputForTargetLanguage('typescript');
8
- outDir;
9
- outFile;
10
- qtSettings;
16
+ outDir: string;
17
+ outFile: string;
18
+ qtSettings: Partial<qt.Options>;
11
19
 
12
20
  constructor(moduleName: string, settings: { outDir?: string; outFile?: string } & Partial<qt.Options> = {}) {
13
21
  this.moduleName = moduleName;
@@ -26,12 +34,12 @@ export class TypeWriter {
26
34
  this.qtSettings = merge(defaultSettings, qtSettings);
27
35
  }
28
36
 
29
- async addMember(name: string, _samples: any[]) {
37
+ async addMember(name: string, _samples: any[]): Promise<void> {
30
38
  const samples = _samples.map(s => (typeof s === 'string' ? s : JSON.stringify(s)));
31
39
  await this.input.addSource({ name, samples });
32
40
  }
33
41
 
34
- async toString() {
42
+ async toString(): Promise<string> {
35
43
  const inputData = new qt.InputData();
36
44
  inputData.addInput(this.input);
37
45
  const result = await qt.quicktype({
@@ -41,7 +49,7 @@ export class TypeWriter {
41
49
  return result.lines.join('\n');
42
50
  }
43
51
 
44
- async toFile() {
52
+ async toFile(): Promise<void> {
45
53
  const result = await this.toString();
46
54
  fs.mkdirSync(this.outDir, { recursive: true });
47
55
  fs.writeFileSync(`${this.outDir}/${this.outFile}`, result);
package/src/snapshot.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { isObjectLike } from 'lodash-es';
2
2
 
3
3
  /**
4
- * Allows special objects (Error, Headers, Set) to be included in JSON.stringify output.
5
- * Functions are removed
4
+ * @deprecated
5
+ * Allows special objects (Error, Headers, Set) to be included in JSON.stringify output. Functions are removed.
6
+ * ⚠️ This is bad! Only use it for debugging!
6
7
  */
7
8
  export function snapshot(i: unknown, max = 50, depth = 0): any {
8
9
  if (Array.isArray(i)) {
package/src/timeout.ts CHANGED
@@ -1,4 +1,4 @@
1
- export async function timeout(ms: number) {
1
+ export async function timeout(ms: number): Promise<void> {
2
2
  return new Promise(resolve => {
3
3
  setTimeout(resolve, ms);
4
4
  });