@getsoda/core 0.2.0 → 0.2.2

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.
Files changed (54) hide show
  1. package/README.md +23 -0
  2. package/dist/app/app.d.ts.map +1 -1
  3. package/dist/app/app.js +17 -0
  4. package/dist/app/app.js.map +1 -1
  5. package/dist/config/config.d.ts +57 -0
  6. package/dist/config/config.d.ts.map +1 -0
  7. package/dist/config/config.js +72 -0
  8. package/dist/config/config.js.map +1 -0
  9. package/dist/config/index.d.ts +9 -0
  10. package/dist/config/index.d.ts.map +1 -0
  11. package/dist/config/index.js +7 -0
  12. package/dist/config/index.js.map +1 -0
  13. package/dist/config/module.d.ts +29 -0
  14. package/dist/config/module.d.ts.map +1 -0
  15. package/dist/config/module.js +28 -0
  16. package/dist/config/module.js.map +1 -0
  17. package/dist/di/container.d.ts +2 -0
  18. package/dist/di/container.d.ts.map +1 -1
  19. package/dist/di/container.js +9 -0
  20. package/dist/di/container.js.map +1 -1
  21. package/dist/http/decorators.d.ts +6 -1
  22. package/dist/http/decorators.d.ts.map +1 -1
  23. package/dist/http/decorators.js.map +1 -1
  24. package/dist/http/index.d.ts +2 -2
  25. package/dist/http/index.d.ts.map +1 -1
  26. package/dist/http/index.js.map +1 -1
  27. package/dist/http/routes.d.ts +10 -1
  28. package/dist/http/routes.d.ts.map +1 -1
  29. package/dist/http/routes.js +4 -1
  30. package/dist/http/routes.js.map +1 -1
  31. package/dist/http/schema.d.ts +2 -0
  32. package/dist/http/schema.d.ts.map +1 -1
  33. package/dist/http/schema.js +2 -1
  34. package/dist/http/schema.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/logger/console-logger.d.ts +12 -4
  40. package/dist/logger/console-logger.d.ts.map +1 -1
  41. package/dist/logger/console-logger.js +47 -17
  42. package/dist/logger/console-logger.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/app/app.ts +24 -1
  45. package/src/config/config.ts +88 -0
  46. package/src/config/index.ts +9 -0
  47. package/src/config/module.ts +40 -0
  48. package/src/di/container.ts +14 -0
  49. package/src/http/decorators.ts +9 -2
  50. package/src/http/index.ts +2 -1
  51. package/src/http/routes.ts +17 -2
  52. package/src/http/schema.ts +2 -1
  53. package/src/index.ts +1 -0
  54. package/src/logger/console-logger.ts +62 -18
@@ -3,12 +3,13 @@
3
3
  * Logger writing to stdout and stderr, readable for people or as JSON lines.
4
4
  *
5
5
  * ```
6
- * 2026-11-13T17:20:03.120Z LOG [UsersService] User created {"id":7}
7
- * {"time":"2026-11-13T17:20:03.120Z","level":"log","context":"UsersService","message":"User created","id":7}
6
+ * [Soda] 41250 - 11/14/2026, 12:22:09 AM LOG [UsersService] User created {"id":7} +3ms
7
+ * {"time":"2026-11-13T21:22:09.120Z","level":"log","context":"UsersService","message":"User created","id":7}
8
8
  * ```
9
9
  *
10
- * Like Nest's `ConsoleLogger`: a minimum level, a JSON mode for log
11
- * collectors; `error` and `fatal` go to stderr, the rest to stdout.
10
+ * Like Nest's `ConsoleLogger`: the same line layout, colored by level in a
11
+ * terminal, with the time since the previous line; a minimum level; a JSON
12
+ * mode for log collectors. `error` and `fatal` go to stderr, the rest to stdout.
12
13
  */
13
14
 
14
15
  import { Logger } from './logger.ts'
@@ -21,10 +22,42 @@ export interface ConsoleLoggerOptions {
21
22
  readonly json?: boolean
22
23
  /** Where lines go. Defaults to stderr for `error` and `fatal`, stdout otherwise. */
23
24
  readonly output?: (line: string, level: LogLevel) => void
25
+ /**
26
+ * Colors by level. Defaults to on when writing to a terminal without
27
+ * `NO_COLOR`, off with a custom `output`.
28
+ */
29
+ readonly colors?: boolean
30
+ /** Name starting each readable line. Defaults to `Soda`. */
31
+ readonly prefix?: string
24
32
  }
25
33
 
26
34
  const SEVERITY: Readonly<Record<LogLevel, number>> = { verbose: 0, debug: 1, log: 2, warn: 3, error: 4, fatal: 5 }
27
35
 
36
+ const color =
37
+ (code: string) =>
38
+ (text: string): string =>
39
+ `\u001B[${code}m${text}\u001B[0m`
40
+
41
+ const LEVEL_COLORS: Readonly<Record<LogLevel, (text: string) => string>> = {
42
+ verbose: color('96'),
43
+ debug: color('95'),
44
+ log: color('32'),
45
+ warn: color('33'),
46
+ error: color('31'),
47
+ fatal: color('1;31'),
48
+ }
49
+ const yellow = color('38;5;3')
50
+ const plain = (text: string): string => text
51
+
52
+ const dateTime = new Intl.DateTimeFormat(undefined, {
53
+ year: 'numeric',
54
+ month: '2-digit',
55
+ day: '2-digit',
56
+ hour: 'numeric',
57
+ minute: '2-digit',
58
+ second: '2-digit',
59
+ })
60
+
28
61
  const writeToConsole = (line: string, level: LogLevel): void => {
29
62
  const stream = level === 'error' || level === 'fatal' ? process.stderr : process.stdout
30
63
 
@@ -75,12 +108,18 @@ export class ConsoleLogger extends Logger {
75
108
  readonly #minimum: number
76
109
  readonly #json: boolean
77
110
  readonly #output: (line: string, level: LogLevel) => void
111
+ readonly #colors: boolean
112
+ readonly #prefix: string
113
+ /** When the previous line was written, for the `+3ms` at the end of the next. */
114
+ #last: number | undefined
78
115
 
79
116
  constructor(options: ConsoleLoggerOptions = {}) {
80
117
  super()
81
118
  this.#minimum = SEVERITY[options.level ?? 'log']
82
119
  this.#json = options.json === true
83
120
  this.#output = options.output ?? writeToConsole
121
+ this.#colors = options.colors ?? (options.output === undefined && process.stdout.isTTY === true && process.env.NO_COLOR === undefined)
122
+ this.#prefix = options.prefix ?? 'Soda'
84
123
  }
85
124
 
86
125
  write(entry: LogEntry): void {
@@ -88,29 +127,34 @@ export class ConsoleLogger extends Logger {
88
127
  return
89
128
  }
90
129
 
91
- const time = new Date().toISOString()
130
+ const now = Date.now()
92
131
 
93
- this.#output(this.#json ? this.#formatJson(entry, time) : this.#formatText(entry, time), entry.level)
132
+ this.#output(this.#json ? this.#formatJson(entry, now) : this.#formatText(entry, now), entry.level)
133
+ this.#last = now
94
134
  }
95
135
 
96
- #formatJson(entry: LogEntry, time: string): string {
136
+ #formatJson(entry: LogEntry, now: number): string {
137
+ const time = new Date(now).toISOString()
138
+
97
139
  // Fixed fields last: data cannot overwrite them.
98
140
  return stringify({ ...entry.data, time, level: entry.level, context: entry.context, message: entry.message }, 'full')
99
141
  }
100
142
 
101
- #formatText(entry: LogEntry, time: string): string {
102
- const context = entry.context === undefined ? '' : `[${entry.context}] `
103
- let line = `${time} ${entry.level.toUpperCase().padEnd(7)} ${context}${entry.message}`
143
+ #formatText(entry: LogEntry, now: number): string {
144
+ const level = this.#colors ? LEVEL_COLORS[entry.level] : plain
145
+ const accent = this.#colors ? yellow : plain
146
+ const context = entry.context === undefined ? '' : accent(`[${entry.context}] `)
147
+ const since = this.#last === undefined ? '' : accent(` +${now - this.#last}ms`)
148
+ const data = entry.data === undefined ? '' : ` ${stringify(entry.data, 'brief')}`
149
+ const line =
150
+ `${level(`[${this.#prefix}] ${process.pid} - `)}${dateTime.format(now)} ${level(entry.level.toUpperCase().padStart(7))} ` +
151
+ `${context}${level(entry.message)}${data}${since}`
104
152
  let stacks = ''
105
153
 
106
- if (entry.data !== undefined) {
107
- line += ` ${stringify(entry.data, 'brief')}`
108
-
109
- // Stacks are read better on their own lines than inside the JSON.
110
- for (const value of Object.values(entry.data)) {
111
- if (value instanceof Error && value.stack !== undefined) {
112
- stacks += `\n${value.stack}`
113
- }
154
+ // Stacks are read better on their own lines than inside the JSON.
155
+ for (const value of Object.values(entry.data ?? {})) {
156
+ if (value instanceof Error && value.stack !== undefined) {
157
+ stacks += `\n${value.stack}`
114
158
  }
115
159
  }
116
160