@holz/json-backend 0.8.2 → 0.8.3-rc.154

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/README.md CHANGED
@@ -1,21 +1,66 @@
1
1
  # `@holz/json-backend`
2
2
 
3
- Prints structured logs to a writable stream in NDJSON form.
3
+ Serializes structured logs to a writable stream in [NDJSON](https://github.com/ndjson/ndjson-spec) format.
4
4
 
5
5
  ## Usage
6
6
 
7
+ This backend serializes logs into a [`WritableStream<Uint8Array>`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). Each chunk is UTF-8 encoded. Ideal for append-only log files.
8
+
9
+ Target environments are servers, host endpoints, and browsers (via [OPFS](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle/createWritable)).
10
+
7
11
  ```typescript
8
12
  import { createJsonBackend } from '@holz/json-backend';
9
13
  import { createLogger } from '@holz/core';
10
- import * as fs from 'node:fs';
11
14
 
12
15
  const logger = createLogger(
13
16
  createJsonBackend({
14
- stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
17
+ stream: new WritableStream<Uint8Array>(
18
+ {
19
+ // ...
20
+ },
21
+ new CountQueuingStrategy({
22
+ highWaterMark: 1024,
23
+ }),
24
+ ),
15
25
  }),
16
26
  );
17
27
  ```
18
28
 
19
- Logs are output in [NDJSON](http://ndjson.org/) format. The output is optimized for log files, following the order of typical log statements. The output includes the log level, timestamp, message, and context, if provided.
29
+ ### Node Streams
30
+
31
+ Node's streams shipped long before the web standard and need to be wrapped with [Writable.toWeb](https://nodejs.org/api/stream.html#streamwritabletowebstreamwritable):
32
+
33
+ ```typescript
34
+ import { createWriteStream } from 'node:fs';
35
+ import { Writable } from 'node:stream';
36
+
37
+ createJsonBackend({
38
+ stream: Writable.toWeb(createWriteStream('logs.ndjson', { flags: 'a' })),
39
+ }),
40
+ ```
41
+
42
+ The same adapter works for any Node writable, including `process.stderr`.
43
+
44
+ ### Closing the Stream
45
+
46
+ By default, the stream remains open forever. Pass an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to control the shutdown.
47
+
48
+ ```typescript
49
+ const controller = new AbortController();
50
+
51
+ createJsonBackend({
52
+ stream: writableStream,
53
+ signal: controller.signal,
54
+ });
55
+
56
+ // Permanently close the writable stream.
57
+ controller.abort();
58
+ ```
59
+
60
+ Tap the stream's [close](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream/WritableStream#closecontroller) handler or node's [finish](https://nodejs.org/api/fs.html#event-finish) event for a clean shutdown.
61
+
62
+ ## Caveats
20
63
 
21
- The `stream` option specifies where the logs will be written to. You can use any writable stream, such as a file or `process.stdout`.
64
+ - Writes are fire-and-forget. Stream backpressure from an overwhelmed sink will cause logs to be dropped. Use [CountQueuingStrategy](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) to adjust the log buffer.
65
+ - Long-running processes and noisy services create giant files. Lean on your environment's tools (`journalctl`) or use a package like [rotating-file-stream](https://github.com/iccicci/rotating-file-stream).
66
+ - OPFS streams in browser environments have poor durability. You may need synchronous access to periodically flush writes, an API only available in workers.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("node:os"),s={fatal:60,error:50,warn:40,info:30,debug:20,trace:10},c=({stream:t})=>e=>{const r=JSON.stringify({level:a[e.level],time:new Date(e.timestamp).toISOString(),msg:e.message,ctx:Object.keys(e.context).length>0?e.context:void 0},o);t.write(`${r}${n.EOL}`)},o=(t,e)=>e instanceof Error?{...e,name:e.name,message:e.message,cause:e.cause}:e,a=Object.fromEntries(Object.entries(s).map(([t,e])=>[e,t]));exports.createJsonBackend=c;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={fatal:60,error:50,warn:40,info:30,debug:20,trace:10},t=(n,r)=>{let i=(e,t,i={})=>{n({timestamp:Date.now(),message:t,level:e,origin:r,context:i})},a={owner:r,trace:i.bind(null,e.trace),debug:i.bind(null,e.debug),info:i.bind(null,e.info),warn:i.bind(null,e.warn),error:i.bind(null,e.error),fatal:i.bind(null,e.fatal),namespace:e=>t(n,r.concat(e)),withMiddleware:e=>t(e(n),r)},o={configurable:!1,enumerable:!1};return Object.defineProperties(a,{withMiddleware:o,namespace:o,trace:o,debug:o,info:o,warn:o,error:o,fatal:o})},n=new TextEncoder,r=({stream:e,signal:t})=>{let r=e.getWriter();return t?.aborted?r.close().catch(i):t?.addEventListener(`abort`,()=>void r.close().catch(i),{once:!0}),e=>{if(r.desiredSize===null||r.desiredSize<=0)return;let t=JSON.stringify({level:o[e.level],time:new Date(e.timestamp).toISOString(),msg:e.message,origin:e.origin,ctx:Object.keys(e.context).length>0?e.context:void 0},a);r.write(n.encode(`${t}\n`)).catch(i)}},i=()=>{},a=(e,t)=>t instanceof Error?{...t,name:t.name,message:t.message,cause:t.cause,...t instanceof AggregateError&&{errors:t.errors}}:t,o=Object.fromEntries(Object.entries(e).map(([e,t])=>[t,e]));exports.createJsonBackend=r;
@@ -1,37 +1,63 @@
1
- import { EOL as n } from "node:os";
2
- const s = {
3
- /** A critical failure happened and the program must exit. */
4
- fatal: 60,
5
- /** Something failed, but we can keep going. */
6
- error: 50,
7
- /** Cause for concern, but we can keep going. */
8
- warn: 40,
9
- /** High-level progress updates. */
10
- info: 30,
11
- /** Verbose update about events or control flow (usually hidden). */
12
- debug: 20,
13
- /** Extremely detailed progress updates (usually hidden). */
14
- trace: 10
15
- }, i = ({ stream: t }) => (e) => {
16
- const r = JSON.stringify(
17
- {
18
- level: o[e.level],
19
- time: new Date(e.timestamp).toISOString(),
20
- msg: e.message,
21
- ctx: Object.keys(e.context).length > 0 ? e.context : void 0
22
- },
23
- c
24
- );
25
- t.write(`${r}${n}`);
26
- }, c = (t, e) => e instanceof Error ? {
27
- ...e,
28
- // Some custom errors have important custom properties.
29
- name: e.name,
30
- message: e.message,
31
- cause: e.cause
32
- } : e, o = Object.fromEntries(
33
- Object.entries(s).map(([t, e]) => [e, t])
34
- );
35
- export {
36
- i as createJsonBackend
37
- };
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 = (n, r) => {
10
+ let i = (e, t, i = {}) => {
11
+ n({
12
+ timestamp: Date.now(),
13
+ message: t,
14
+ level: e,
15
+ origin: r,
16
+ context: i
17
+ });
18
+ }, a = {
19
+ owner: r,
20
+ trace: i.bind(null, e.trace),
21
+ debug: i.bind(null, e.debug),
22
+ info: i.bind(null, e.info),
23
+ warn: i.bind(null, e.warn),
24
+ error: i.bind(null, e.error),
25
+ fatal: i.bind(null, e.fatal),
26
+ namespace: (e) => t(n, r.concat(e)),
27
+ withMiddleware: (e) => t(e(n), r)
28
+ }, o = {
29
+ configurable: !1,
30
+ enumerable: !1
31
+ };
32
+ return Object.defineProperties(a, {
33
+ withMiddleware: o,
34
+ namespace: o,
35
+ trace: o,
36
+ debug: o,
37
+ info: o,
38
+ warn: o,
39
+ error: o,
40
+ fatal: o
41
+ });
42
+ }, n = new TextEncoder(), r = ({ stream: e, signal: t }) => {
43
+ let r = e.getWriter();
44
+ return t?.aborted ? r.close().catch(i) : t?.addEventListener("abort", () => void r.close().catch(i), { once: !0 }), (e) => {
45
+ if (r.desiredSize === null || r.desiredSize <= 0) return;
46
+ let t = JSON.stringify({
47
+ level: o[e.level],
48
+ time: new Date(e.timestamp).toISOString(),
49
+ msg: e.message,
50
+ origin: e.origin,
51
+ ctx: Object.keys(e.context).length > 0 ? e.context : void 0
52
+ }, a);
53
+ r.write(n.encode(`${t}\n`)).catch(i);
54
+ };
55
+ }, i = () => {}, a = (e, t) => t instanceof Error ? {
56
+ ...t,
57
+ name: t.name,
58
+ message: t.message,
59
+ cause: t.cause,
60
+ ...t instanceof AggregateError && { errors: t.errors }
61
+ } : t, o = Object.fromEntries(Object.entries(e).map(([e, t]) => [t, e]));
62
+ //#endregion
63
+ export { r as createJsonBackend };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { createJsonBackend } from './json-backend';
@@ -0,0 +1,30 @@
1
+ import { LogProcessor } from '@holz/core';
2
+ /**
3
+ * Prints structured logs to a writable stream in NDJSON form. Optimized for
4
+ * log files.
5
+ *
6
+ * Writes to a Web Streams `WritableStream<Uint8Array>` so the same backend
7
+ * runs across Node, Deno, Bun, and the browser (e.g. OPFS). See the readme
8
+ * for a Node file-stream example.
9
+ *
10
+ * @example
11
+ * createJsonBackend({
12
+ * stream: new WritableStream({
13
+ * write: (chunk) => void process.stdout.write(chunk),
14
+ * }),
15
+ * })
16
+ *
17
+ * @see https://github.com/ndjson/ndjson-spec
18
+ */
19
+ export declare const createJsonBackend: ({ stream, signal }: Config) => LogProcessor;
20
+ interface Config {
21
+ /** Where to print logs. Each log is written as a line of UTF-8 NDJSON. */
22
+ stream: WritableStream<Uint8Array>;
23
+ /**
24
+ * Closes the stream on abort, flushing queued writes and committing
25
+ * OPFS-style sinks. Fire-and-forget: to await the flush, observe your own
26
+ * sink or resource completion. Logs are dropped once shutdown begins.
27
+ */
28
+ signal?: AbortSignal;
29
+ }
30
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holz/json-backend",
3
- "version": "0.8.2",
3
+ "version": "0.8.3-rc.154+3f5148e",
4
4
  "description": "Print logs as newline-delimited JSON.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -32,18 +32,18 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "prepack": "vite build",
35
- "test:unit": "vitest --color --passWithNoTests",
35
+ "test:unit": "vitest run --color --passWithNoTests",
36
36
  "test:types": "tsc"
37
37
  },
38
38
  "devDependencies": {
39
- "@holz/core": "^0.8.2",
40
- "@types/node": "^22.0.0",
41
- "@vitest/coverage-v8": "^3.0.8",
42
- "typescript": "^5.8.2",
43
- "vite": "^6.0.0",
44
- "vite-plugin-dts": "^4.5.3",
45
- "vite-tsconfig-paths": "^5.1.4",
46
- "vitest": "^3.0.8"
39
+ "@holz/core": "^0.8.3-rc.154+3f5148e",
40
+ "@types/node": "^24.0.0",
41
+ "@vitest/coverage-v8": "^4.0.0",
42
+ "typescript": "^6.0.0",
43
+ "vite": "^8.0.0",
44
+ "vite-plugin-dts": "^5.0.0",
45
+ "vite-tsconfig-paths": "^6.0.0",
46
+ "vitest": "^4.0.0"
47
47
  },
48
- "gitHead": "f099a09cd01834de9bc1714908183cbb7b828c85"
48
+ "gitHead": "3f5148eb94bb1d7200d4b4993c95ea30db76b0c4"
49
49
  }
@@ -1,22 +0,0 @@
1
- import { LogProcessor } from '@holz/core';
2
- import { Writable } from 'node:stream';
3
-
4
- declare interface Config {
5
- /** Where to print logs. */
6
- stream: Writable;
7
- }
8
-
9
- /**
10
- * Prints structured logs to a writable stream in NDJSON form. Optimized for
11
- * log files.
12
- *
13
- * @example
14
- * createJsonBackend({
15
- * stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
16
- * })
17
- *
18
- * @see http://ndjson.org
19
- */
20
- export declare const createJsonBackend: ({ stream }: Config) => LogProcessor;
21
-
22
- export { }