@holz/json-backend 0.9.1-rc.8 → 0.9.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.
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={fatal:60,error:50,warn:40,info:30,debug:20,trace:10},t=({stream:e})=>t=>{let i=JSON.stringify({level:r[t.level],time:new Date(t.timestamp).toISOString(),msg:t.message,origin:t.origin,ctx:Object.keys(t.context).length>0?t.context:void 0},n);e.write(`${i}\n`)},n=(e,t)=>t instanceof Error?{...t,name:t.name,message:t.message,cause:t.cause,...t instanceof AggregateError&&{errors:t.errors}}:t,r=Object.fromEntries(Object.entries(e).map(([e,t])=>[t,e]));exports.createJsonBackend=t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@holz/core");var t=({stream:e})=>t=>{let i=JSON.stringify({level:r[t.level],time:new Date(t.timestamp).toISOString(),msg:t.message,origin:t.origin,ctx:Object.keys(t.context).length>0?t.context:void 0},n);e.write(`${i}\n`)},n=(e,t)=>t instanceof Error?{...t,name:t.name,message:t.message,cause:t.cause,...t instanceof AggregateError&&{errors:t.errors}}:t,r=Object.fromEntries(Object.entries(e.level).map(([e,t])=>[t,e]));exports.createJsonBackend=t;
@@ -1,12 +1,6 @@
1
- //#region ../holz-core/dist/holz-core.js
2
- var e = {
3
- fatal: 60,
4
- error: 50,
5
- warn: 40,
6
- info: 30,
7
- debug: 20,
8
- trace: 10
9
- }, t = ({ stream: e }) => (t) => {
1
+ import { level as e } from "@holz/core";
2
+ //#region src/json-backend.ts
3
+ var t = ({ stream: e }) => (t) => {
10
4
  let i = JSON.stringify({
11
5
  level: r[t.level],
12
6
  time: new Date(t.timestamp).toISOString(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holz/json-backend",
3
- "version": "0.9.1-rc.8+6bcf3fb",
3
+ "version": "0.9.1",
4
4
  "description": "Print logs as newline-delimited JSON.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
13
14
  "types": "./dist/holz-json-backend.d.ts",
14
15
  "require": "./dist/holz-json-backend.cjs",
15
16
  "import": "./dist/holz-json-backend.js"
@@ -22,7 +23,9 @@
22
23
  "license": "MIT",
23
24
  "sideEffects": false,
24
25
  "files": [
25
- "dist"
26
+ "dist",
27
+ "src",
28
+ "!src/**/__tests__"
26
29
  ],
27
30
  "keywords": [
28
31
  "holz-backend",
@@ -36,16 +39,19 @@
36
39
  "test:coverage": "vitest run --coverage --color --passWithNoTests",
37
40
  "test:types": "tsc"
38
41
  },
42
+ "peerDependencies": {
43
+ "@holz/core": "^0.9.0"
44
+ },
39
45
  "devDependencies": {
40
- "@holz/core": "^0.9.1-rc.8+6bcf3fb",
46
+ "@holz/core": "^0.9.1",
41
47
  "@microsoft/api-extractor": "^7.58.8",
42
48
  "@types/node": "^24.0.0",
43
- "@vitest/coverage-v8": "^4.0.0",
49
+ "@vitest/coverage-v8": "^5.0.0",
44
50
  "typescript": "^6.0.0",
45
51
  "vite": "^8.0.0",
46
52
  "vite-plugin-dts": "^5.0.0",
47
53
  "vite-tsconfig-paths": "^6.0.0",
48
- "vitest": "^4.0.0"
54
+ "vitest": "^5.0.0"
49
55
  },
50
- "gitHead": "6bcf3fbc31a0cb3597e62832bfa672535fcaa144"
56
+ "gitHead": "87f40467fe9aa798fa894c04a208751403c45e8c"
51
57
  }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { createJsonBackend } from './json-backend';
@@ -0,0 +1,73 @@
1
+ import type { Writable } from 'node:stream';
2
+ import { level, type LogLevel, type Log, type LogProcessor } from '@holz/core';
3
+
4
+ /**
5
+ * Prints structured logs to a writable stream in NDJSON form. Optimized for
6
+ * log files.
7
+ *
8
+ * @example
9
+ * createJsonBackend({
10
+ * stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
11
+ * })
12
+ *
13
+ * @see https://github.com/ndjson/ndjson-spec
14
+ */
15
+ export const createJsonBackend = ({ stream }: Config): LogProcessor => {
16
+ return (log: Log) => {
17
+ // Follow the order of typical log statements. Be kind to the human
18
+ // reader.
19
+ const output = JSON.stringify(
20
+ {
21
+ level: labelForLevel[log.level],
22
+ time: new Date(log.timestamp).toISOString(),
23
+ msg: log.message,
24
+ origin: log.origin,
25
+ ctx: Object.keys(log.context).length > 0 ? log.context : undefined,
26
+ },
27
+ errorSerializer,
28
+ );
29
+
30
+ // NOTE: If the stream applies backpressure, we will lose logs. I believe
31
+ // this is the right tradeoff. We can't prevent the app from generating
32
+ // more logs, and if we buffered it would risk running out of memory and
33
+ // crashing the process.
34
+ //
35
+ // It is unlikely that a file or tty will apply backpressure in practice.
36
+ stream.write(`${output}\n`);
37
+ };
38
+ };
39
+
40
+ /**
41
+ * Errors are not JSON serializable, but errors are naturally a crucial aspect
42
+ * of any logging system. This unwraps errors into a JSON representation.
43
+ */
44
+ const errorSerializer = (_key: string, value: unknown) => {
45
+ if (value instanceof Error) {
46
+ return {
47
+ ...value, // Some custom errors have important custom properties.
48
+ name: value.name,
49
+ message: value.message,
50
+ cause: value.cause,
51
+ // `AggregateError` collects its constituent failures in a
52
+ // non-enumerable `errors` array that the spread above misses. Pull it
53
+ // explicitly so those underlying errors survive serialization; the
54
+ // replacer recurses into each one.
55
+ ...(value instanceof AggregateError && { errors: value.errors }),
56
+ };
57
+ }
58
+
59
+ return value;
60
+ };
61
+
62
+ /**
63
+ * Slightly heavier than logging the number, but easier to process
64
+ * programmatically which is somewhat implied by a JSON backend.
65
+ */
66
+ const labelForLevel = Object.fromEntries(
67
+ Object.entries(level).map(([key, value]) => [value, key]),
68
+ ) as Record<LogLevel, string>;
69
+
70
+ interface Config {
71
+ /** Where to print logs. */
72
+ stream: Writable;
73
+ }