Skip to content

Commit f76e7f9

Browse files
authored
Merge pull request #90 from NillionNetwork/release/1.0.0
2 parents 6c1190d + 1a135f6 commit f76e7f9

File tree

9 files changed

+553
-359
lines changed

9 files changed

+553
-359
lines changed

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nillion/nuc",
3-
"version": "1.0.0-rc.2",
3+
"version": "1.0.0-rc.3",
44
"license": "MIT",
55
"repository": "https://github.com/NillionNetwork/nuc-ts",
66
"engines": {
@@ -28,8 +28,8 @@
2828
},
2929
"dependencies": {
3030
"@bufbuild/protobuf": "^2.9.0",
31-
"@cosmjs/proto-signing": "^0.36.0",
32-
"@cosmjs/stargate": "^0.36.0",
31+
"@cosmjs/proto-signing": "^0.36.1",
32+
"@cosmjs/stargate": "^0.36.1",
3333
"@noble/curves": "^2.0.1",
3434
"@noble/hashes": "^2.0.1",
3535
"@scure/base": "^2.0.0",
@@ -48,9 +48,9 @@
4848
"@commitlint/cli": "^20.1.0",
4949
"@commitlint/config-conventional": "^20.0.0",
5050
"@commitlint/types": "^20.0.0",
51-
"@keplr-wallet/types": "^0.12.277",
51+
"@keplr-wallet/types": "^0.12.279",
5252
"@types/debug": "^4.1.12",
53-
"@types/node": "^24.6.0",
53+
"@types/node": "^24.7.0",
5454
"@types/semver": "^7.7.1",
5555
"@vitest/coverage-v8": "^3.2.4",
5656
"docker-compose": "^1.3.0",
@@ -62,8 +62,8 @@
6262
"tsx": "^4.20.6",
6363
"type-fest": "^5.0.1",
6464
"typedoc": "^0.28.13",
65-
"typescript": "^5.9.2",
66-
"vite": "^7.1.7",
65+
"typescript": "^5.9.3",
66+
"vite": "^7.1.9",
6767
"vite-tsconfig-paths": "^5.1.4",
6868
"vitest": "^3.2.4"
6969
},

pnpm-lock.yaml

Lines changed: 255 additions & 255 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/logger.ts

Lines changed: 135 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
1-
import pino, { type Level, type Logger } from "pino";
1+
/**
2+
* @module logger
3+
* Provides a standardized, environment-aware logging utility for the library.
4+
*
5+
* This module configures a `pino` logger instance that can be controlled via
6+
* environment variables in Node.js or `localStorage` in the browser. It also
7+
* exposes a global API (`window.__NILLION`) for runtime log level management
8+
* in browser environments.
9+
*
10+
* @example
11+
* ```bash
12+
* # In Node.js
13+
* NILLION_LOG_LEVEL=debug node my-script.js
14+
* ```
15+
*
16+
* @example
17+
* ```ts
18+
* // In a browser's developer console
19+
* localStorage.setItem("NILLION_LOG_LEVEL", "debug");
20+
* // Or, to control all Nillion libraries on the page at once:
21+
* __NILLION.setLogLevel("debug");
22+
* ```
23+
*/
24+
import pino from "pino";
225

3-
export type LogLevel = Level | "silent";
26+
export type LogLevel = pino.Level | "silent";
427

528
const LOG_LEVEL_KEY = "NILLION_LOG_LEVEL";
629
const DEFAULT_LOG_LEVEL: LogLevel = "silent";
7-
const VALID_LOG_LEVELS: ReadonlyArray<LogLevel> = [
30+
const VALID_LOG_LEVELS: ReadonlySet<string> = new Set<LogLevel>([
831
"fatal",
932
"error",
1033
"warn",
1134
"info",
1235
"debug",
1336
"trace",
1437
"silent",
15-
];
38+
]);
1639

40+
/**
41+
* A resilient wrapper around `localStorage` that fails gracefully in non-browser environments.
42+
* @internal
43+
*/
1744
const safeStorage = {
1845
getItem: (key: string): string | null => {
1946
try {
20-
// Use optional chaining for resilience in non-browser environments.
2147
return globalThis.localStorage?.getItem(key) ?? null;
2248
} catch {
2349
return null;
@@ -27,55 +53,58 @@ const safeStorage = {
2753
try {
2854
globalThis.localStorage?.setItem(key, value);
2955
} catch {
30-
// Ignore errors if storage is disabled
56+
// Fails silently if storage is unavailable.
3157
}
3258
},
3359
removeItem: (key: string): void => {
3460
try {
3561
globalThis.localStorage?.removeItem(key);
3662
} catch {
37-
// Ignore errors
63+
// Fails silently if storage is unavailable.
3864
}
3965
},
4066
};
4167

68+
/**
69+
* Type guard to check if a value is a valid `LogLevel`.
70+
* @internal
71+
*/
4272
function isValidLogLevel(value: unknown): value is LogLevel {
43-
return (
44-
typeof value === "string" && VALID_LOG_LEVELS.includes(value as LogLevel)
45-
);
73+
return typeof value === "string" && VALID_LOG_LEVELS.has(value);
4674
}
4775

76+
/**
77+
* Determines the initial log level with a clear order of precedence.
78+
* @internal
79+
*/
4880
function getInitialLogLevel(): LogLevel {
49-
let level = DEFAULT_LOG_LEVEL;
50-
const sources = [
51-
process.env?.[LOG_LEVEL_KEY],
52-
safeStorage.getItem(LOG_LEVEL_KEY),
53-
// @ts-expect-error
54-
globalThis[LOG_LEVEL_KEY],
55-
];
56-
57-
for (const source of sources) {
58-
const levelAttempt = source?.toLowerCase();
59-
if (isValidLogLevel(levelAttempt)) {
60-
level = levelAttempt;
61-
break;
62-
}
81+
const fromEnv =
82+
typeof process !== "undefined" ? process.env[LOG_LEVEL_KEY] : undefined;
83+
if (isValidLogLevel(fromEnv)) {
84+
return fromEnv;
6385
}
6486

65-
return level;
66-
}
87+
const fromStorage = safeStorage.getItem(LOG_LEVEL_KEY);
88+
if (isValidLogLevel(fromStorage)) {
89+
return fromStorage;
90+
}
6791

68-
function getLoggerForEnv(): Logger<never, boolean> {
69-
const level = getInitialLogLevel();
70-
return pino({
71-
level,
72-
browser: { asObject: true },
73-
base: null,
74-
});
92+
return DEFAULT_LOG_LEVEL;
7593
}
7694

77-
export const Log = getLoggerForEnv();
95+
/**
96+
* The shared, singleton logger instance for this library module.
97+
*/
98+
export const Log: pino.Logger = pino({
99+
level: getInitialLogLevel(),
100+
browser: { asObject: true },
101+
base: null,
102+
});
78103

104+
/**
105+
* Sets the log level for this specific logger instance and persists the choice.
106+
* @param level The new log level to set.
107+
*/
79108
export function setLogLevel(level: LogLevel): void {
80109
if (!isValidLogLevel(level)) {
81110
console.warn(`[Logger] Invalid log level: "${level}". Ignoring.`);
@@ -85,28 +114,90 @@ export function setLogLevel(level: LogLevel): void {
85114
safeStorage.setItem(LOG_LEVEL_KEY, level);
86115
}
87116

117+
/**
118+
* Returns the current log level of this specific logger instance.
119+
*/
88120
export function getLogLevel(): LogLevel {
89121
return Log.level as LogLevel;
90122
}
91123

124+
/**
125+
* Removes the stored log level from `localStorage`.
126+
*/
92127
export function clearStoredLogLevel(): void {
93128
safeStorage.removeItem(LOG_LEVEL_KEY);
94129
}
95130

96-
interface NillionGlobal {
97-
setLogLevel: (level: LogLevel) => void;
98-
getLogLevel: () => LogLevel;
99-
clearStoredLogLevel: () => void;
131+
/**
132+
* The interface for a single logger's control functions.
133+
* @internal
134+
*/
135+
interface LoggerApi {
136+
set: (level: LogLevel) => void;
137+
get: () => LogLevel;
138+
clear: () => void;
100139
}
101140

102141
declare global {
103-
var __NILLION: NillionGlobal;
142+
// eslint-disable-next-line no-var
143+
var __NILLION: {
144+
/**
145+
* A set of all registered Nillion logger instances on the page.
146+
* @internal
147+
*/
148+
_instances: Set<LoggerApi>;
149+
/**
150+
* Sets the log level for all Nillion libraries on the page.
151+
*/
152+
setLogLevel: (level: LogLevel) => void;
153+
/**
154+
* Gets the log level of the first registered Nillion library.
155+
* Note: If different libraries have different levels, this may not be representative.
156+
*/
157+
getLogLevel: () => LogLevel;
158+
/**
159+
* Clears the persisted log level setting from localStorage.
160+
*/
161+
clearStoredLogLevel: () => void;
162+
};
104163
}
105164

165+
/**
166+
* Attaches a global controller to `globalThis` for managing all logger instances.
167+
* This handles the case where multiple libraries using this logger are present.
168+
* @internal
169+
*/
106170
if (typeof globalThis !== "undefined") {
107-
globalThis.__NILLION = {
108-
setLogLevel,
109-
getLogLevel,
110-
clearStoredLogLevel,
111-
};
171+
// Initialize the global controller only once.
172+
if (!globalThis.__NILLION) {
173+
const instances = new Set<LoggerApi>();
174+
globalThis.__NILLION = {
175+
_instances: instances,
176+
setLogLevel: (level: LogLevel) => {
177+
// Broadcast the command to all registered logger instances.
178+
for (const instance of instances) {
179+
instance.set(level);
180+
}
181+
},
182+
getLogLevel: (): LogLevel => {
183+
// Return the level of the first registered instance as a representative value.
184+
const first = instances.values().next().value;
185+
return first ? first.get() : getInitialLogLevel();
186+
},
187+
clearStoredLogLevel: () => {
188+
// This is a global action, so only one instance needs to perform it.
189+
const first = instances.values().next().value;
190+
if (first) {
191+
first.clear();
192+
}
193+
},
194+
};
195+
}
196+
197+
// Register this specific logger instance's API with the global controller.
198+
globalThis.__NILLION._instances.add({
199+
set: setLogLevel,
200+
get: getLogLevel,
201+
clear: clearStoredLogLevel,
202+
});
112203
}

0 commit comments

Comments
 (0)