@fend/firo 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,312 @@
1
+ // src/utils.ts
2
+ var LOG_LEVELS = {
3
+ debug: 20,
4
+ info: 30,
5
+ warn: 40,
6
+ error: 50
7
+ };
8
+ var FIRO_COLORS = {
9
+ // Basic ANSI (safe for any terminal)
10
+ cyan: "36",
11
+ green: "32",
12
+ yellow: "33",
13
+ magenta: "35",
14
+ blue: "34",
15
+ brightCyan: "96",
16
+ brightGreen: "92",
17
+ brightYellow: "93",
18
+ brightMagenta: "95",
19
+ brightBlue: "94",
20
+ // Extended 256-color palette
21
+ orange: "38;5;214",
22
+ pink: "38;5;213",
23
+ lilac: "38;5;141",
24
+ skyBlue: "38;5;117",
25
+ mint: "38;5;156",
26
+ salmon: "38;5;210",
27
+ lemon: "38;5;228",
28
+ lavender: "38;5;183",
29
+ sage: "38;5;114",
30
+ coral: "38;5;209",
31
+ teal: "38;5;116",
32
+ rose: "38;5;219",
33
+ pistachio: "38;5;150",
34
+ mauve: "38;5;175",
35
+ aqua: "38;5;81",
36
+ gold: "38;5;222",
37
+ thistle: "38;5;182",
38
+ seafoam: "38;5;115",
39
+ tangerine: "38;5;208",
40
+ periwinkle: "38;5;147"
41
+ };
42
+ var COLORS_LIST = Object.values(FIRO_COLORS);
43
+ var SAFE_COLORS_COUNT = 10;
44
+ var getColorIndex = (str, useAllColors = false) => {
45
+ let hash = 0;
46
+ str.split("").forEach((char) => {
47
+ hash = char.charCodeAt(0) + ((hash << 5) - hash);
48
+ });
49
+ const range = useAllColors ? COLORS_LIST.length : SAFE_COLORS_COUNT;
50
+ return Math.abs(hash % range);
51
+ };
52
+ var colorize = (text, colorIndex, color) => {
53
+ const code = color ?? (COLORS_LIST[colorIndex] || COLORS_LIST[colorIndex % SAFE_COLORS_COUNT]);
54
+ return `\x1B[${code}m${text}\x1B[0m`;
55
+ };
56
+ var colorizeLevel = (level, text) => {
57
+ if (level === "info") return text;
58
+ switch (level) {
59
+ case "error":
60
+ return `\x1B[31m${text}\x1B[0m`;
61
+ // Red
62
+ case "warn":
63
+ return `\x1B[33m${text}\x1B[0m`;
64
+ // Yellow
65
+ case "debug":
66
+ return `\x1B[2m${text}\x1B[0m`;
67
+ // Dim
68
+ default:
69
+ return text;
70
+ }
71
+ };
72
+
73
+ // src/transports.ts
74
+ import { inspect } from "util";
75
+ import process from "process";
76
+ import fs from "fs";
77
+ var createDevTransport = (config = {}) => {
78
+ const locale = config.locale ?? void 0;
79
+ const timeOpts = {
80
+ hour12: false,
81
+ hour: "2-digit",
82
+ minute: "2-digit",
83
+ second: "2-digit",
84
+ fractionalSecondDigits: 3,
85
+ ...config.timeOptions || {}
86
+ };
87
+ const transport = (level, context, msg, data, opts) => {
88
+ const now = /* @__PURE__ */ new Date();
89
+ const timestamp = now.toLocaleTimeString(locale, timeOpts);
90
+ const contextStr = context.map((ctx) => {
91
+ const key = ctx.options?.omitKey ? "" : `${ctx.key}:`;
92
+ const content = `${key}${ctx.value}`;
93
+ return colorize(`[${content}]`, ctx.options.colorIndex, ctx.options.color);
94
+ }).join(" ");
95
+ if (level === "error" && data === void 0) {
96
+ const realError = wrapToError(msg);
97
+ data = realError;
98
+ msg = realError.message;
99
+ }
100
+ let dataStr = "";
101
+ if (data !== void 0) {
102
+ const inspectOptions = opts?.pretty ? { compact: false, colors: true, depth: null } : { compact: true, breakLength: Infinity, colors: true, depth: null };
103
+ dataStr = inspect(data, inspectOptions);
104
+ }
105
+ const msgStr = typeof msg === "object" && msg !== null ? inspect(msg, { colors: true, compact: true, breakLength: Infinity }) : String(msg);
106
+ const parts = [
107
+ `[${timestamp}]`,
108
+ // Normal (not dimmed)
109
+ contextStr,
110
+ level === "error" ? colorizeLevel("error", `[ERROR] ${msgStr}`) : level === "warn" ? colorizeLevel("warn", `[WARN] ${msgStr}`) : level === "debug" ? colorizeLevel("debug", msgStr) : msgStr,
111
+ level === "debug" && dataStr ? `\x1B[2m${dataStr.replace(/\x1b\[0m/g, "\x1B[0m\x1B[2m")}\x1B[0m` : dataStr
112
+ ];
113
+ let finalLine = parts.filter(Boolean).join(" ") + "\n";
114
+ if (level === "error") process.stderr.write(finalLine);
115
+ else process.stdout.write(finalLine);
116
+ };
117
+ return transport;
118
+ };
119
+ var safeStringify = (obj) => {
120
+ try {
121
+ return JSON.stringify(obj);
122
+ } catch {
123
+ return inspect(obj);
124
+ }
125
+ };
126
+ var wrapToError = (obj) => {
127
+ if (obj instanceof Error) return obj;
128
+ return new Error(
129
+ typeof obj === "object" && obj !== null ? safeStringify(obj) : String(obj)
130
+ );
131
+ };
132
+ var serializeError = (_err) => {
133
+ const err = wrapToError(_err);
134
+ return {
135
+ message: err.message,
136
+ stack: err.stack,
137
+ name: err.name,
138
+ ...err
139
+ };
140
+ };
141
+ var buildRecord = (level, context, msg, data) => {
142
+ const contextObj = context.reduce((acc, item) => {
143
+ acc[item.key] = item.value;
144
+ return acc;
145
+ }, {});
146
+ const logRecord = {
147
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
148
+ level,
149
+ ...contextObj
150
+ };
151
+ if (level === "error") {
152
+ const message = typeof msg === "string" ? msg : msg instanceof Error ? msg.message : typeof msg === "object" && msg !== null ? safeStringify(msg) : String(msg);
153
+ logRecord.message = message;
154
+ if (data instanceof Error) {
155
+ logRecord.error = serializeError(data);
156
+ } else if (msg instanceof Error) {
157
+ logRecord.error = serializeError(msg);
158
+ if (data !== void 0) logRecord.data = data;
159
+ } else {
160
+ logRecord.error = serializeError(msg);
161
+ if (data !== void 0) logRecord.data = data;
162
+ }
163
+ } else {
164
+ logRecord.message = typeof msg === "object" && msg !== null ? safeStringify(msg) : String(msg);
165
+ if (data !== void 0) {
166
+ logRecord.data = data instanceof Error ? serializeError(data) : data;
167
+ }
168
+ }
169
+ return logRecord;
170
+ };
171
+ var createJsonTransport = (config = {}) => {
172
+ const queue = [];
173
+ const maxQueueSize = config.maxQueueSize ?? 1e3;
174
+ let isDraining = false;
175
+ const flush = () => {
176
+ if (queue.length === 0 || isDraining) return;
177
+ while (queue.length > 0) {
178
+ const line = queue.shift();
179
+ const ok = process.stdout.write(line);
180
+ if (!ok) {
181
+ isDraining = true;
182
+ process.stdout.once("drain", () => {
183
+ isDraining = false;
184
+ flush();
185
+ });
186
+ return;
187
+ }
188
+ }
189
+ };
190
+ const flushSync = () => {
191
+ while (queue.length > 0) {
192
+ const line = queue.shift();
193
+ if (line) {
194
+ try {
195
+ fs.writeSync(1, line);
196
+ } catch {
197
+ }
198
+ }
199
+ }
200
+ };
201
+ if (config.async) {
202
+ process.on("beforeExit", flushSync);
203
+ process.on("exit", flushSync);
204
+ }
205
+ return (level, context, msg, data) => {
206
+ const record = buildRecord(level, context, msg, data);
207
+ let line;
208
+ try {
209
+ line = JSON.stringify(record) + "\n";
210
+ } catch {
211
+ if (record.data) record.data = inspect(record.data);
212
+ try {
213
+ line = JSON.stringify(record) + "\n";
214
+ } catch {
215
+ line = JSON.stringify({
216
+ timestamp: record.timestamp,
217
+ level,
218
+ message: record.message,
219
+ error: "Failed to serialize log record"
220
+ }) + "\n";
221
+ }
222
+ }
223
+ if (!config.async) {
224
+ process.stdout.write(line);
225
+ return;
226
+ }
227
+ queue.push(line);
228
+ if (queue.length > maxQueueSize) {
229
+ queue.shift();
230
+ }
231
+ flush();
232
+ };
233
+ };
234
+
235
+ // src/index.ts
236
+ var fillContextItem = (item, useAllColors = false) => {
237
+ return {
238
+ ...item,
239
+ options: {
240
+ colorIndex: item.options && typeof item.options.colorIndex === "number" ? item.options.colorIndex : getColorIndex(item.key, useAllColors),
241
+ color: item.options?.color,
242
+ omitKey: item.options?.omitKey ?? false
243
+ }
244
+ };
245
+ };
246
+ var createLoggerInternal = (config, parentContext) => {
247
+ const useAllColors = config.useAllColors ?? false;
248
+ const fill = (item) => fillContextItem(item, useAllColors);
249
+ const appendContextWithInvokeContext = (context2, invokeContext) => {
250
+ if (!invokeContext || invokeContext.length === 0) return context2;
251
+ return [...context2, ...invokeContext.map(fill)];
252
+ };
253
+ const context = [...parentContext.map(fill)];
254
+ const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport({ async: config.async }) : createDevTransport(config.devTransportConfig));
255
+ const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
256
+ const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
257
+ const getContext = () => context;
258
+ const addContext = (key, value, options) => {
259
+ let item = typeof key === "string" ? { key, value, options } : key;
260
+ context.push(fill(item));
261
+ };
262
+ const removeKeyFromContext = (key) => {
263
+ const index = context.findIndex((ctx) => ctx.key === key);
264
+ if (index !== -1) context.splice(index, 1);
265
+ };
266
+ const child = (ctx) => {
267
+ const newItems = Object.entries(ctx).map(([key, value]) => ({
268
+ key,
269
+ value,
270
+ options: { colorIndex: getColorIndex(key, useAllColors) }
271
+ }));
272
+ return createLoggerInternal({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
273
+ };
274
+ const debug = (msg, data, opts) => {
275
+ if (minLevel > LOG_LEVELS.debug) return;
276
+ transport("debug", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
277
+ };
278
+ const info = (msg, data, opts) => {
279
+ if (minLevel > LOG_LEVELS.info) return;
280
+ transport("info", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
281
+ };
282
+ const warn = (msg, data, opts) => {
283
+ if (minLevel > LOG_LEVELS.warn) return;
284
+ transport("warn", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
285
+ };
286
+ const error = (msgOrError, err, opts) => {
287
+ if (minLevel > LOG_LEVELS.error) return;
288
+ transport("error", appendContextWithInvokeContext(context, opts?.ctx), msgOrError, err, opts);
289
+ };
290
+ const logInstance = ((msg, data, opts) => {
291
+ info(msg, data, opts);
292
+ });
293
+ return Object.assign(logInstance, {
294
+ debug,
295
+ info,
296
+ warn,
297
+ error,
298
+ child,
299
+ addContext,
300
+ getContext,
301
+ removeFromContext: removeKeyFromContext
302
+ });
303
+ };
304
+ var createLogger = (config = {}) => {
305
+ return createLoggerInternal(config, []);
306
+ };
307
+ export {
308
+ FIRO_COLORS,
309
+ createDevTransport,
310
+ createJsonTransport,
311
+ createLogger
312
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@fend/firo",
3
+ "version": "0.0.1",
4
+ "description": "Elegant logger for Node.js, Bun and Deno with brilliant DX.",
5
+ "keywords": [
6
+ "logger",
7
+ "pino",
8
+ "firo",
9
+ "fend",
10
+ "dx",
11
+ "structured-logging",
12
+ "ndjson",
13
+ "color",
14
+ "zero-dependencies"
15
+ ],
16
+ "author": "Alex Saft <fend25@gmail.com>",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/fend25/firo.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/fend25/firo/issues"
24
+ },
25
+ "homepage": "https://github.com/fend25/firo#readme",
26
+ "type": "module",
27
+ "main": "./dist/index.cjs",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "require": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "publish:jsr": "pnpm dlx jsr publish",
43
+ "test": "node --import tsx --test test/*.test.ts",
44
+ "typecheck": "tsc --noEmit",
45
+ "demo": "tsx demo.ts"
46
+ },
47
+ "engines": {
48
+ "node": ">=16"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.5.0",
52
+ "tsup": "^8.5.1",
53
+ "tsx": "^4.0.0",
54
+ "typescript": "^5.9.3"
55
+ }
56
+ }