@hatua/log 0.0.0 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Gomes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Levelled, categorised diagnostics for the parts of Hatua that decide things
3
+ * without drawing them.
4
+ *
5
+ * The store refuses a command, narrows a publish gate, halts a save, drops a
6
+ * stale renewal — each for a reason it knows exactly and, until now, kept. What
7
+ * reaches a screen is the outcome; what is worth having while something is
8
+ * being worked on is the reasoning that led there, in order, with what it was
9
+ * looking at.
10
+ *
11
+ * ## Silent unless asked
12
+ *
13
+ * Hatua renders inside somebody else's product. A library that writes `info` to
14
+ * a Host's console by default is one every integrator has to go and turn off,
15
+ * and one whose noise buries their own logs — so nothing below `warn` is
16
+ * written until a caller asks for it. `warn` and `error` are always through,
17
+ * because a Host that has wired something wrongly should hear about it without
18
+ * having to opt in.
19
+ *
20
+ * ## Categories are packages
21
+ *
22
+ * A category names where the line came from — `services.editing`,
23
+ * `react.fields` — so a level can be turned up for the thing being chased
24
+ * without turning it up for everything. Dotted, and matched by prefix:
25
+ * `services` covers `services.editing`, and `*` covers everything.
26
+ */
27
+ export type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace';
28
+ /**
29
+ * What a line is handed to.
30
+ *
31
+ * A seam rather than a hard-wired `console`, because a Host that already has
32
+ * somewhere for diagnostics to go should not have Hatua's arriving somewhere
33
+ * else — and because a test that asserts what was logged should not have to
34
+ * read the terminal to do it.
35
+ */
36
+ export type Sink = (line: Line) => void;
37
+ export interface Line {
38
+ level: Level;
39
+ /** Where it came from, dotted: `services.editing`. */
40
+ category: string;
41
+ message: string;
42
+ /** Whatever the call site was looking at when it decided. */
43
+ detail?: Record<string, unknown>;
44
+ }
45
+ export interface LogConfig {
46
+ /**
47
+ * The level a category is written at, by prefix. The longest matching prefix
48
+ * wins, so `{ '*': 'warn', 'services.editing': 'trace' }` says what it looks
49
+ * like it says.
50
+ */
51
+ levels?: Record<string, Level>;
52
+ sink?: Sink;
53
+ }
54
+ /**
55
+ * The settings, held on the global rather than in this module.
56
+ *
57
+ * A module holds its state once per instance, and there is no promise that this
58
+ * module is instantiated once. A dev server resolving symlinked workspace
59
+ * packages, a Host bundling `@hatua/react` while its own code imports
60
+ * `@hatua/log`, two copies at different versions in one tree — each gives the
61
+ * app one table and the packages another, so turning a category up changes a
62
+ * table nothing reads and the switch appears to do nothing at all.
63
+ *
64
+ * A symbol on `globalThis` is the one place every instance can agree on. It is
65
+ * what the settings are ABOUT — one page, one answer to "what is Hatua writing"
66
+ * — so sharing them there is the shape of the thing rather than a workaround
67
+ * for the bundler.
68
+ */
69
+ interface Settings {
70
+ levels: Record<string, Level>;
71
+ sink: Sink;
72
+ }
73
+ /**
74
+ * Turn categories up or down, or send lines somewhere else.
75
+ *
76
+ * Merged into what is already set rather than replacing it, so turning one
77
+ * category up does not silently reset the rest. Returns what is in force, so a
78
+ * console can show whether the call took.
79
+ */
80
+ export declare function configureLogging(config: LogConfig): Readonly<Settings>;
81
+ /**
82
+ * Turn a level on from code, in one line, with the spec a person types.
83
+ *
84
+ * What a `console.log` used to be for. Chasing something means putting a line
85
+ * where the question is, and going through `configureLogging({ levels: … })`
86
+ * asks for two imports and a nested object at the moment attention is
87
+ * elsewhere — so this is the form that gets pasted at the top of a file and
88
+ * deleted an hour later.
89
+ *
90
+ * import { setLogLevel } from '@hatua/log'
91
+ * setLogLevel('*:debug')
92
+ *
93
+ * It complains rather than doing nothing when the spec is unusable, because a
94
+ * diagnostic switch that fails silently is worse than none: the silence reads
95
+ * as "nothing is happening" when it means "nothing was turned on".
96
+ */
97
+ export declare function setLogLevel(spec: string): Readonly<Settings>;
98
+ /** Back to silent-unless-asked, and back to the console. What a test resets to. */
99
+ export declare function resetLogging(): void;
100
+ /**
101
+ * Levels from a short string: `*:debug`, `services.editing:trace,react:debug`.
102
+ *
103
+ * Here rather than in whatever is calling it because it is the form a person
104
+ * types under time pressure — into a console, into a query string, into an
105
+ * environment variable — and every one of those callers would otherwise write
106
+ * its own splitter and get a different one wrong.
107
+ *
108
+ * A bare level with no category means everything: `debug` is `*:debug`.
109
+ */
110
+ export declare function levelsFrom(spec: string): Record<string, Level>;
111
+ export interface Logger {
112
+ error(message: string, detail?: Record<string, unknown>): void;
113
+ warn(message: string, detail?: Record<string, unknown>): void;
114
+ info(message: string, detail?: Record<string, unknown>): void;
115
+ debug(message: string, detail?: Record<string, unknown>): void;
116
+ trace(message: string, detail?: Record<string, unknown>): void;
117
+ /** Whether a line at this level would be written, for a caller with work to do to build one. */
118
+ enabled(level: Level): boolean;
119
+ }
120
+ /**
121
+ * A logger for one category.
122
+ *
123
+ * Held at module scope by its caller — the category is a fact about the file,
124
+ * not about the call — and it reads the configuration at write time, so turning
125
+ * a category up affects loggers already made.
126
+ */
127
+ export declare function logger(category: string): Logger;
128
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
1
+ //#region src/index.ts
2
+ var e = {
3
+ error: 0,
4
+ warn: 1,
5
+ info: 2,
6
+ debug: 3,
7
+ trace: 4
8
+ }, t = { "*": "warn" }, n = {
9
+ error: "#e5484d",
10
+ warn: "#d9a300",
11
+ info: "#8b8b8b",
12
+ debug: "#8b8b8b",
13
+ trace: "#8b8b8b"
14
+ }, r = typeof window < "u", i = ({ level: e, category: t, message: i, detail: a }) => {
15
+ let o = e === "error" ? console.error : e === "warn" ? console.warn : console.log, s = `[${e.toUpperCase()}][${t}]`;
16
+ if (!r) {
17
+ let e = `${s} ${i}`;
18
+ a === void 0 ? o(e) : o(e, a);
19
+ return;
20
+ }
21
+ let c = `%c${s}%c ${i}`, l = `color:${n[e]};font-weight:bold`, u = `color:${n[e]};font-weight:normal`;
22
+ a === void 0 ? o(c, l, u) : o(c, l, u, a);
23
+ }, a = Symbol.for("hatua.log.settings"), o = globalThis, s = () => (o[a] ??= {
24
+ levels: { ...t },
25
+ sink: i
26
+ }, o[a]);
27
+ function c(e) {
28
+ let t = s();
29
+ return e.levels && (t.levels = {
30
+ ...t.levels,
31
+ ...e.levels
32
+ }), e.sink && (t.sink = e.sink), {
33
+ levels: { ...t.levels },
34
+ sink: t.sink
35
+ };
36
+ }
37
+ function l(e) {
38
+ let t = f(e);
39
+ return Object.keys(t).length === 0 && console.warn(`[hatua] nothing usable in the log level "${e}". Try '*:debug'.`), c({ levels: t });
40
+ }
41
+ function u() {
42
+ o[a] = {
43
+ levels: { ...t },
44
+ sink: i
45
+ };
46
+ }
47
+ var d = (e) => {
48
+ let { levels: t } = s(), n = t["*"] ?? "warn", r = -1;
49
+ for (let [i, a] of Object.entries(t)) i !== "*" && (e === i || e.startsWith(`${i}.`)) && i.length > r && (r = i.length, n = a);
50
+ return n;
51
+ };
52
+ function f(e) {
53
+ let t = {};
54
+ for (let n of e.split(",")) {
55
+ let e = n.trim();
56
+ if (!e) continue;
57
+ let r = e.lastIndexOf(":"), i = r < 0 ? "*" : e.slice(0, r).trim(), a = (r < 0 ? e : e.slice(r + 1)).trim();
58
+ p(a) && (t[i || "*"] = a);
59
+ }
60
+ return t;
61
+ }
62
+ var p = (t) => t in e;
63
+ function m(t) {
64
+ let n = (n, r, i) => {
65
+ e[n] > e[d(t)] || s().sink({
66
+ level: n,
67
+ category: t,
68
+ message: r,
69
+ ...i === void 0 ? {} : { detail: i }
70
+ });
71
+ };
72
+ return {
73
+ error: (e, t) => n("error", e, t),
74
+ warn: (e, t) => n("warn", e, t),
75
+ info: (e, t) => n("info", e, t),
76
+ debug: (e, t) => n("debug", e, t),
77
+ trace: (e, t) => n("trace", e, t),
78
+ enabled: (n) => e[n] <= e[d(t)]
79
+ };
80
+ }
81
+ //#endregion
82
+ export { c as configureLogging, f as levelsFrom, m as logger, u as resetLogging, l as setLogLevel };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,17 +1,31 @@
1
1
  {
2
2
  "name": "@hatua/log",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving the name. Nothing is published here; see 0.1.0 and later.",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Internal to @hatua/react — published because it is an external of its build, not a supported API. Levelled, categorised diagnostics — silent unless a Host asks.",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/pedromvgomes/hatua.git",
9
10
  "directory": "source/packages/log"
10
11
  },
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
11
22
  "publishConfig": {
12
23
  "access": "public"
13
24
  },
14
- "files": [
15
- "README.md"
16
- ]
25
+ "scripts": {
26
+ "build": "vite build",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run",
29
+ "test:coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reporter=lcovonly"
30
+ }
17
31
  }
package/README.md DELETED
@@ -1,5 +0,0 @@
1
- # @hatua/log
2
-
3
- This version reserves the package name and contains no code.
4
-
5
- Install `0.1.0` or later.