@better-logger/core 0.6.0 โ†’ 0.8.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/README.md CHANGED
@@ -1,294 +1,248 @@
1
- # @better-logger/core
2
-
3
- > Track what your app *did*, not what it *said*.
4
-
5
- Execution flow debugger for modern apps. Zero deps. Works everywhere. <10KB.
6
-
7
- ```bash
8
- npm install @better-logger/core
9
- ```
10
-
11
- ---
12
-
13
- Stop debugging like this:
14
-
15
- ```ts
16
- console.log('start')
17
- console.log('user', user)
18
- console.log('after db')
19
- console.log('done')
20
- ```
21
-
22
- And trying to mentally reconstruct what happened, in what order, and how long each thing took.
23
-
24
- Your app is not a list of messages.
25
-
26
- It's a **flow**.
27
-
28
- ---
29
-
30
- ## What you actually get
31
-
32
- ```
33
- ๐Ÿš€ [flow:user-onboarding] [auth] (tid: a1b2c3d4)
34
- โ†’ create-user
35
- data: { email: "test@example.com" }
36
- โœ“ create-user (45ms)
37
- โ†’ generate-ai-profile
38
- โœ“ generate-ai-profile (410ms)
39
- โ†’ save-db
40
- โœ— save-db (error: timeout)
41
- ๐Ÿ [flow:user-onboarding] failed (532ms)
42
- ```
43
-
44
- ```ts
45
- import { createFlow } from '@better-logger/core'
46
-
47
- const flow = createFlow('user-onboarding', { tags: ['auth'] })
48
-
49
- const s1 = flow.step('create-user', { email })
50
- await createUser(email)
51
- s1.success()
52
-
53
- const s2 = flow.step('save-db')
54
- await saveUser()
55
- s2.fail(new Error('timeout'))
56
-
57
- flow.fail(new Error('save-db failed'))
58
- ```
59
-
60
- You didn't write logs.
61
-
62
- You defined the **execution**.
63
-
64
- better-logger turns your code into a timeline of what actually happened.
65
-
66
- Output is deferred until `flow.success()` / `flow.fail()` โ€” one atomic write, zero interleaving, even in async chaos.
67
-
68
- ---
69
-
70
- ## Why this exists
71
-
72
- Debugging today is broken:
73
-
74
- - `console.log` โ†’ flat noise, no structure
75
- - No automatic timing
76
- - No context propagation
77
- - Async flows become impossible to follow
78
-
79
- You don't debug code.
80
-
81
- You debug **guesses about what happened**.
82
-
83
- better-logger removes the guesswork. Instead of scattering `console.log` across your code and reconstructing timelines mentally, you define the flow upfront and get a structured narrative back โ€” with timing, context, and clear failure markers.
84
-
85
- ---
86
-
87
- ## What changes
88
-
89
- Instead of guessing:
90
- - "did this run?"
91
- - "what ran first?"
92
- - "where did it fail?"
93
- - "why is it slow?"
94
-
95
- You **see**:
96
-
97
- - ๐Ÿง  A full execution narrative
98
- - โฑ Exact timing for every step
99
- - ๐ŸŒณ Nested flows (real hierarchy, not flat logs)
100
- - ๐Ÿงพ Data at the moment it mattered
101
- - ๐Ÿงต Shared context across the entire flow
102
- - ๐Ÿท Tags to group and filter
103
- - ๐Ÿงฑ Guaranteed consistency โ€” no race-condition bugs
104
- - ๐Ÿ”’ Immutable render snapshots โ€” output can't be corrupted
105
-
106
- This replaces `console.log` for anything non-trivial.
107
-
108
- ---
109
-
110
- ## Why not just use something else?
111
-
112
- | Tool | Reality |
113
- |------|--------|
114
- | `console.log` | You reconstruct flows manually like it's 2005 |
115
- | `pino` / `winston` | Built for shipping logs, not understanding execution |
116
- | `debug` | Namespaces โ‰  structure |
117
- | OpenTelemetry | Powerful, but heavy and not built for everyday debugging |
118
-
119
- **@better-logger/core** sits in the missing gap:
120
-
121
- โ†’ instant, local, zero-config execution tracing
122
-
123
- ---
124
-
125
- ## Usage
126
-
127
- ```ts
128
- const flow = createFlow('request', { tags: ['api'] })
129
-
130
- const step = flow.step('db')
131
- await db.query()
132
- step.success()
133
-
134
- flow.success()
135
- ```
136
-
137
- That's it.
138
-
139
- ---
140
-
141
- ## How it works
142
-
143
- ```mermaid
144
- graph TD
145
- Start[createFlow] --> Step[step]
146
- Step --> Complete[success / fail]
147
- Complete --> FlowEnd[flow.success / flow.fail]
148
- FlowEnd --> Render[Atomic Render]
149
- ```
150
-
151
- ---
152
-
153
- ## When should you use this
154
-
155
- Use it when:
156
-
157
- - your flow has more than 2 steps
158
- - async logic gets confusing
159
- - debugging requires more than one `console.log`
160
- - timing matters
161
- - you need to know "what ran" and "what didn't"
162
-
163
- If a single `console.log` feels enough, you don't need this.
164
-
165
- If it doesn'tโ€ฆ this replaces it.
166
-
167
- ---
168
-
169
- ## Advanced
170
-
171
- ### Child Flows
172
-
173
- ```ts
174
- const flow = createFlow('request')
175
-
176
- const ai = flow.child('ai-call')
177
- const s = ai.step('generate')
178
- await generate()
179
- s.success()
180
- ai.success()
181
-
182
- flow.success()
183
- ```
184
-
185
- ```
186
- ๐Ÿš€ [flow:request] (tid: a1b2c3d4)
187
- โ””โ”€ ๐Ÿš€ [flow:ai-call] (tid: x9y8z7w6)
188
- โ†’ generate
189
- โœ“ generate (210ms)
190
- ๐Ÿ [flow:ai-call] success (215ms)
191
- ๐Ÿ [flow:request] success (340ms)
192
- ```
193
-
194
- ### Context Propagation
195
-
196
- ```ts
197
- flow.setContext({ userId: 'abc', requestId: 'xyz' })
198
- // Available across all steps and child flows
199
- ```
200
-
201
- ### Silent Mode
202
-
203
- ```ts
204
- const flow = createFlow('task', { enabled: false })
205
- // Near-zero overhead. All operations are no-ops.
206
- ```
207
-
208
- ### Max Steps Guard
209
-
210
- ```ts
211
- const flow = createFlow('batch', { maxSteps: 1000 })
212
- // Prevents memory blowups from runaway loops
213
- ```
214
-
215
- ### Subscribe (DevTools / Testing)
216
-
217
- ```ts
218
- import { subscribe, toJSON } from '@better-logger/core'
219
-
220
- subscribe((trace) => {
221
- // Frozen snapshot of every completed flow
222
- console.log(toJSON(trace))
223
- })
224
- ```
225
-
226
- ### JSON Output
227
-
228
- ```ts
229
- import { toJSON } from '@better-logger/core'
230
-
231
- const json = toJSON(flow)
232
- // { version: 1, flow: { id, name, state, status, tags, steps, context, ... } }
233
- ```
234
-
235
- Portable, structured trace for tooling, testing, and future integrations.
236
-
237
- ---
238
-
239
- ## Features
240
-
241
- ### Core
242
- `createFlow` ยท `step` ยท `success/fail` ยท `child` ยท `toJSON`
243
-
244
- ### Advanced
245
- Context propagation ยท Tags ยท Inline data snapshots ยท Subscribe event tap ยท Deferred atomic rendering ยท Immutable snapshots
246
-
247
- ### Safety
248
- Guarded finalization ยท `maxSteps` overflow protection ยท Lifecycle states (`running` โ†’ `completed`) ยท Safe data serializer (circular refs, deep nesting) ยท NoOp singletons (zero-allocation silent mode)
249
-
250
- ---
251
-
252
- ## What's not included
253
-
254
- - โŒ Log levels (debug, info, warn)
255
- - โŒ Transports (file, HTTP, Elasticsearch)
256
- - โŒ SaaS dashboards
257
- - โŒ Auto-instrumentation
258
-
259
- This is a developer debugging tool, not observability infrastructure.
260
-
261
- ---
262
-
263
- ## Roadmap
264
-
265
- | Version | Focus |
266
- |---------|-------|
267
- | **V1** | Core flow debugger โ€” this release |
268
- | **V2** | Custom renderers, `flow.run()` async helper, flow sampling |
269
- | **V3** | Browser DevTools timeline panel |
270
- | **V4+** | Python / Go / Rust ports, shared tracing schema |
271
-
272
- ---
273
-
274
- ## Organization
275
-
276
- Published under the **@better-logger** org, owned by [0xmilord](https://github.com/0xmilord).
277
-
278
- ```bash
279
- npm install @better-logger/core
280
- ```
281
-
282
- All packages in this org share the same execution tracing schema.
283
-
284
- ---
285
-
286
- ## Test Coverage
287
-
288
- **100%** across all metrics โ€” statements, branches, functions, lines.
289
-
290
- [View detailed coverage report](./COVERAGE.md) | [Testing Strategy](./docs/TESTING-STRATEGY.md)
291
-
292
- ## License
293
-
294
- MIT
1
+ # better-logger
2
+
3
+ **Track what your app *did*, not what it *said*.**
4
+
5
+ Execution flow debugger for modern apps. Zero deps. Works everywhere. <10KB.
6
+
7
+ ```bash
8
+ npm install @better-logger/core
9
+ ```
10
+
11
+ ## Stop debugging like this:
12
+
13
+ ```typescript
14
+ console.log('start')
15
+ console.log('user', user)
16
+ console.log('after db')
17
+ console.log('done')
18
+ ```
19
+
20
+ And trying to mentally reconstruct what happened, in what order, and how long each thing took.
21
+
22
+ ## Your app is not a list of messages. It's a flow.
23
+
24
+ ```typescript
25
+ import { better } from '@better-logger/core'
26
+
27
+ // Replace console.log โ€” zero learning curve
28
+ better.log('User created', { email: 'test@example.com' })
29
+ better.log.warn('Slow query detected', { duration: 250 })
30
+ better.log.error('Payment failed', error)
31
+
32
+ // When you need structure
33
+ const flow = better.flow('user-onboarding', { tags: ['auth'] })
34
+ const step = flow.step('create-user', { email })
35
+ await createUser(email)
36
+ step.success()
37
+ flow.success()
38
+ ```
39
+
40
+ **What you get:**
41
+
42
+ ```
43
+ ๐Ÿš€ [flow:user-onboarding] [auth] (tid: a1b2c3d4)
44
+ โ†’ create-user
45
+ data: { email: "test@example.com" }
46
+ โœ“ create-user (45ms)
47
+ ๐Ÿ [flow:user-onboarding] success (45ms)
48
+ ```
49
+
50
+ ## Why this exists
51
+
52
+ Debugging today is broken:
53
+
54
+ | Problem | better-logger solution |
55
+ |---------|----------------------|
56
+ | `console.log` โ†’ flat noise, no structure | Hierarchical flows with automatic timing |
57
+ | No automatic timing | Every step and flow tracks duration |
58
+ | No context propagation | `flow.setContext()` shares metadata across all steps |
59
+ | Async flows become impossible to follow | Deferred atomic rendering โ€” one output per flow |
60
+ | Need to learn new API | `better.log()` is `console.log()` โ€” just add `.log` |
61
+
62
+ ## Quick Start
63
+
64
+ ### 1. Drop-in replacement
65
+
66
+ Replace `console.log` with `better.log`:
67
+
68
+ ```typescript
69
+ // Before
70
+ console.log('Server started on port 3000')
71
+ console.error('Database connection failed', error)
72
+
73
+ // After
74
+ import { better } from '@better-logger/core'
75
+
76
+ better.log('Server started on port 3000')
77
+ better.log.error('Database connection failed', error)
78
+ ```
79
+
80
+ That's it. You now get structured, timed output for free.
81
+
82
+ ### 2. When you need structure
83
+
84
+ For important operations, use explicit flows:
85
+
86
+ ```typescript
87
+ const flow = better.flow('process-payment', { tags: ['billing'] })
88
+ flow.setContext({ orderId: 'ord-123', userId: 'user-456' })
89
+
90
+ const step = flow.step('charge-card', { amount: 99.99 })
91
+ const result = await paymentGateway.charge({ amount: 99.99 })
92
+ step.success(result)
93
+
94
+ flow.success()
95
+ better.log(`Payment processed: ${result.transactionId}`)
96
+ ```
97
+
98
+ ### 3. Async made easy
99
+
100
+ ```typescript
101
+ const flow = better.flow('data-export')
102
+
103
+ const data = await flow.run('fetch-data', async () => {
104
+ return await db.users.find({})
105
+ })
106
+
107
+ const file = await flow.run('generate-file', async () => {
108
+ return await exportToCSV(data)
109
+ })
110
+
111
+ flow.success()
112
+ ```
113
+
114
+ ## API Reference
115
+
116
+ ### `better.log()` โ€” The simple way
117
+
118
+ ```typescript
119
+ better.log('message') // Simple message
120
+ better.log('message', data) // Message with data
121
+ better.log.info('message', data) // Info (same as log)
122
+ better.log.warn('message', data) // Warning (auto-tags flow)
123
+ better.log.error('message', error) // Error (fails flow)
124
+ ```
125
+
126
+ ### `better.flow()` โ€” The structured way
127
+
128
+ ```typescript
129
+ const flow = better.flow('name', {
130
+ tags: ['tag1', 'tag2'], // Indexing labels
131
+ maxSteps: 100, // Safety limit
132
+ initialContext: { key: 'value' } // Shared metadata
133
+ })
134
+
135
+ flow.setContext({ userId: 'abc' }) // Add context
136
+ flow.getContext() // Get context copy
137
+
138
+ flow.step('step-name', data) // Create step
139
+ .success() // Mark success
140
+ .fail(error) // Mark failure
141
+
142
+ flow.success() // Complete flow
143
+ flow.fail(error) // Fail flow
144
+ ```
145
+
146
+ ### `better.flow.run()` โ€” Async helper
147
+
148
+ ```typescript
149
+ const result = await flow.run('async-step', async () => {
150
+ return await fetchData() // Auto-completes step on success/fail
151
+ })
152
+ ```
153
+
154
+ ### Configuration
155
+
156
+ ```typescript
157
+ better.log.setIdleTimeout(200) // Auto-complete timeout (ms)
158
+ better.log.setFlowName('my-app') // Default flow name
159
+ better.log.setDefaultTags(['app', 'v3']) // Tags on all flows
160
+ better.log.flush() // Force complete current flow
161
+ better.setEnabled(false) // Disable all logging
162
+ better.isEnabled() // Check if enabled
163
+ ```
164
+
165
+ ## Real-World Examples
166
+
167
+ We've included 10 comprehensive examples showing better-logger in real applications:
168
+
169
+ | Example | What it shows |
170
+ |---------|--------------|
171
+ | `01-express-rest-api.js` | Express middleware, CRUD endpoints, error handling |
172
+ | `02-background-job-processor.js` | Job queue, retry logic, nested flows |
173
+ | `03-authentication-flow.js` | Login, token refresh, password reset, security logging |
174
+ | `04-ecommerce-order-processing.js` | Multi-step checkout, inventory, payment, shipping |
175
+ | `05-cli-tool.js` | CLI commands, progress tracking, file processing |
176
+ | `06-api-integration-client.js` | HTTP client, retry logic, rate limiting, batch ops |
177
+ | `07-microservice-distributed-tracing.js` | Trace context, downstream calls, health checks |
178
+ | `08-testing-utilities.js` | Test runner, assertions, performance testing |
179
+ | `09-data-pipeline.js` | ETL pipelines, transforms, validation, aggregation |
180
+ | `10-full-application.js` | Complete Node.js app with all patterns combined |
181
+
182
+ ## Features
183
+
184
+ ### Zero Friction
185
+ - `better.log()` is a drop-in for `console.log()`
186
+ - No configuration needed โ€” import and use immediately
187
+ - Auto-flow management groups rapid calls together
188
+ - Idle timeout auto-completes flows (100ms default)
189
+
190
+ ### Structured Output
191
+ - Hierarchical flows with visual nesting
192
+ - Automatic timing for every step and flow
193
+ - Context propagation across all steps
194
+ - Tags for filtering and grouping
195
+ - JSON export for downstream systems
196
+
197
+ ### Production-Grade
198
+ - Zero dependencies โ€” nothing to break
199
+ - <10KB bundle โ€” won't bloat your app
200
+ - Works in Node.js 18+, browsers, Edge runtimes
201
+ - Full TypeScript support
202
+ - 100% test coverage
203
+
204
+ ### Forgiving
205
+ - Call methods after completion โ€” silent no-ops
206
+ - Pass any data type โ€” handled gracefully
207
+ - Circular references โ€” won't crash
208
+ - Concurrent flows โ€” no interleaving
209
+ - Error anywhere โ€” won't break your app
210
+
211
+ ## Migration from console.log
212
+
213
+ ```typescript
214
+ // Before
215
+ console.log('User created:', user)
216
+ console.warn('Slow query', { duration: 250 })
217
+ console.error('Payment failed:', error)
218
+
219
+ // After (literally just add "better.")
220
+ import { better } from '@better-logger/core'
221
+
222
+ better.log('User created:', user)
223
+ better.log.warn('Slow query', { duration: 250 })
224
+ better.log.error('Payment failed:', error)
225
+ ```
226
+
227
+ **Migration effort:** 10 minutes to replace all console.log. Hours to days to adopt flows strategically where they add value.
228
+
229
+ ## What's NOT included
230
+
231
+ - โŒ Log levels (debug, info, warn, error) โ€” execution tracing โ‰  log routing
232
+ - โŒ Transport layers (file, HTTP, Elasticsearch) โ€” zero-config philosophy
233
+ - โŒ SaaS dashboards โ€” we emit data, we don't host it
234
+ - โŒ Auto-instrumentation โ€” devs control what they trace
235
+
236
+ ## Package Health
237
+
238
+ | Metric | Value |
239
+ |--------|-------|
240
+ | Bundle size | <10KB minified |
241
+ | Dependencies | 0 |
242
+ | Test coverage | 100% |
243
+ | TypeScript | Full support |
244
+ | Runtimes | Node 18+, Browser, Edge |
245
+
246
+ ## License
247
+
248
+ MIT
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";var N=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var r in t)N(e,r,{get:t[r],enumerable:!0})},oe=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of te(t))!re.call(e,i)&&i!==r&&N(e,i,{get:()=>t[i],enumerable:!(n=ee(t,i))||n.enumerable});return e};var ie=e=>oe(N({},"__esModule",{value:!0}),e);var Oe={};ne(Oe,{NOOP_FLOW:()=>l,NOOP_STEP:()=>m,better:()=>d,createFlow:()=>v,getRenderer:()=>D,isEnabled:()=>C,normalizeError:()=>f,safeSerialize:()=>u,setClock:()=>X,setEnabled:()=>Q,setIdGenerator:()=>V,setRenderer:()=>P,subscribe:()=>L,toJSON:()=>W});module.exports=ie(Oe);function f(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var S=class{constructor(t,r){this._trace=t;this._clock=r;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(t){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=f(t))}};var g=()=>{},m={success:g,fail:g},l={step:()=>m,child:()=>l,tag:g,tags:()=>[],setContext:g,getContext:()=>({}),success:g,fail:g,run:(e,t)=>{try{let r=t();return r instanceof Promise?r:Promise.resolve(r)}catch(r){return Promise.reject(r)}}};function J(e){let t=new WeakMap;function r(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(t.has(n))return t.get(n);if(Array.isArray(n)){let s=[];t.set(n,s);for(let o=0;o<n.length;o++)s.push(r(n[o]));return s}let i={};t.set(n,i);for(let s of Object.keys(n))i[s]=r(n[s]);return i}return r(e)}function k(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let t of e)k(t);else for(let t of Object.keys(e)){let r=e[t];r&&typeof r=="object"&&!Object.isFrozen(r)&&k(r)}return e}function b(e){let t=J(e);return k(t)}var T=new Set;function L(e){return T.add(e),()=>{T.delete(e)}}function se(e){if(T.size!==0)for(let t of T)try{t(e)}catch{}}var y=class e{constructor(t,r,n,i,s){this._trace=t;this._clock=r;this._idGen=n;this._renderer=i;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(t,r){if(this._completed)return m;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,m;let i={id:this._idGen.generate(),name:t,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:r,children:[]};return this._trace.steps.push(i),new S(i,this._clock)}child(t){if(this._completed)return l;if(this._options.enabled===!1)return l;let r={id:this._idGen.generate(),name:t,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(r,this._clock,this._idGen,this._renderer,{...this._options})}tag(t){this._completed||this._trace.tags.includes(t)||this._trace.tags.push(t)}tags(){return[...this._trace.tags]}setContext(t){this._completed||Object.assign(this._trace.context,t)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(t){this._trace.error=f(t),this._finalize("error")}run(t,r){if(this._completed)return Promise.resolve(r());let n=this.step(t);return Promise.resolve(r()).then(i=>(n.success(),i),i=>{throw n.fail(i),i})}_finalize(t){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=t;let r=b(this._trace);se(r),this._renderer.render(r)}};var x=class{generate(){return Math.random().toString(36).slice(2,10)}};var h=class{now(){return Date.now()}};function u(e,t){let r=t?.maxDepth??2,n=t?.maxLength??80,i=new WeakSet;function s(o,c){if(typeof o=="function")return`[Fn:${o.name||"?"}]`;if(typeof o=="symbol")return o.toString();if(c>r)return o&&typeof o=="object"?"[...]":o;if(o===null||typeof o!="object")return o;if(o instanceof Map)return`{Map(${o.size})}`;if(o instanceof Set)return`{Set(${o.size})}`;if(o instanceof Date)return o.toISOString();if(o instanceof RegExp)return o.toString();if(i.has(o))return"[Circular]";if(i.add(o),Array.isArray(o))return o.map(p=>s(p,c+1));let _={};for(let p of Object.keys(o)){let E=o[p];_[p]=s(E,c+1)}return _}try{let o=JSON.stringify(s(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var ae=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,le=()=>typeof EdgeRuntime<"u",ce=()=>typeof caches<"u"&&"default"in caches,de=()=>typeof window<"u"||typeof self<"u";function q(){return ae()?"node":le()||ce()?"edge":de()?"browser":"node"}var fe="\x1B[0m",ue="\x1B[32m",pe="\x1B[31m";var me="\x1B[36m";var ge="\x1B[2m";function we(){return!(q()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var xe=we();function R(e,t){return xe?`${t}${e}${fe}`:e}function z(e){return R(e,ue)}function O(e){return R(e,pe)}function U(e){return R(e,me)}function I(e){return R(e,ge)}function B(e,t){if(t===void 0)return"\u2014";let r=Math.round(t-e);return r>=1e3?`${(r/1e3).toFixed(1)}s`:`${r}ms`}function Y(e,t,r){let n=" ".repeat(t);r.push(`${n}${U("\u2192")} ${e.name}`),e.data!==void 0&&r.push(`${n} ${I("data:")} ${u(e.data,{maxLength:80})}`);let i=B(e.start,e.end);e.status==="success"?r.push(`${n}${z("\u2713")} ${e.name} (${i})`):e.status==="error"?r.push(`${n}${O("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):r.push(`${n} ${e.name} (${i})`);for(let s of e.children)Y(s,t+1,r)}var F=class{constructor(){this.name="console"}render(t){let r=[],n=t.tags.length>0?` [${t.tags.join(", ")}]`:"",i=t.status==="error"?"\u{1F525}":"\u{1F680}";r.push(`${i} [flow:${t.name}]${n} (${I("tid:")} ${t.id})`);for(let c of t.steps)Y(c,1,r);let s=B(t.start,t.end),o=t.meta?.maxStepsExceeded?` ${O("\u26A0\uFE0F maxSteps exceeded")}`:"";t.status==="error"?(r.push(`\u{1F3C1} [flow:${t.name}] ${O("failed")} (${s}, error: ${t.error?.message??"Unknown error"})${o}`),console.error(r.join(`
2
- `))):(r.push(`\u{1F3C1} [flow:${t.name}] ${z("success")} (${s})${o}`),console.log(r.join(`
3
- `)))}};var H=null;function P(e){H=e}function D(){return H}function K(e){return H??e}var G=new x,A=new h,_e=new F,M=!0;function Q(e){M=e}function C(){return M}function V(e){G=e}function X(e){A=e}function v(e,t){if(!M)return l;let r=t?.sample??1;if(r<1&&Math.random()>r)return l;if(t?.enabled===!1)return l;let n=K(_e),i={id:G.generate(),name:e,state:"running",tags:t?.tags??[],start:A.now(),context:t?.initialContext?{...t.initialContext}:{},steps:[]};return new y(i,A,G,n,t??{})}var w={idleTimeout:100,flowName:"default",defaultTags:[]},a=null;function Se(e){if(a&&e===void 0&&!a.completed)return a.flow;let t=e??w.flowName,r=v(t,{tags:[...w.defaultTags]});return a={flow:r,timer:null,lastError:null,completed:!1},r}function ke(){a&&(a.timer&&clearTimeout(a.timer),a.timer=setTimeout(()=>{Z()},w.idleTimeout))}function Z(){!a||a.completed||(a.timer&&(clearTimeout(a.timer),a.timer=null),a.completed=!0,a.lastError?a.flow.fail(a.lastError):a.flow.success(),a=null)}function Te(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function $(e,t,r,n){if(!C())return;let i,s;t===void 0?(i=void 0,s=void 0):Te(t)?r===void 0?(i=void 0,s=t):(i=void 0,s={...t,...r}):(i=t,s=r);let o=s?.flow,c=Se(o);if(s?.tags)for(let E of s.tags)c.tag(E);let _=n&&n!=="info"?`${e} [${n}]`:e,p=c.step(_,i);n==="error"?(p.fail(i),a.lastError=i,c.tag("error")):(p.success(),n&&n!=="info"&&c.tag(n)),ke()}var ye=(e,t,r)=>$(e,t,r),d=ye;d.info=(e,t,r)=>$(e,t,r,"info");d.warn=(e,t,r)=>$(e,t,r,"warn");d.error=(e,t,r)=>$(e,t,r,"error");d.setIdleTimeout=e=>{w.idleTimeout=e};d.setFlowName=e=>{w.flowName=e};d.setDefaultTags=e=>{w.defaultTags=e};d.activeFlow=()=>a?.flow??null;d.flush=()=>{Z()};function Re(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function W(e){let t=Re(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:t.id,name:t.name,...t.parentId!==void 0&&{parentId:t.parentId},state:t.state,...t.status!==void 0&&{status:t.status},tags:t.tags,start:t.start,...t.end!==void 0&&{end:t.end},...t.error!==void 0&&{error:t.error},context:t.context,steps:t.steps.map(j),...t.meta!==void 0&&{meta:t.meta}}},null,2)}function j(e){let t={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(t.end=e.end),e.data!==void 0)try{t.data=JSON.parse(u(e.data))}catch{t.data="[Circular or unserializable]"}return e.error!==void 0&&(t.error=e.error),e.children.length>0&&(t.children=e.children.map(j)),t}0&&(module.exports={NOOP_FLOW,NOOP_STEP,better,createFlow,getRenderer,isEnabled,normalizeError,safeSerialize,setClock,setEnabled,setIdGenerator,setRenderer,subscribe,toJSON});
1
+ "use strict";var I=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var ie=Object.prototype.hasOwnProperty;var se=(e,r)=>{for(var t in r)I(e,t,{get:r[t],enumerable:!0})},ae=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of oe(r))!ie.call(e,i)&&i!==t&&I(e,i,{get:()=>r[i],enumerable:!(n=ne(r,i))||n.enumerable});return e};var le=e=>ae(I({},"__esModule",{value:!0}),e);var ve={};se(ve,{NOOP_FLOW:()=>c,NOOP_STEP:()=>m,better:()=>d,createFlow:()=>k,getRenderer:()=>A,isEnabled:()=>x,normalizeError:()=>f,safeSerialize:()=>p,setClock:()=>j,setEnabled:()=>b,setIdGenerator:()=>Z,setRenderer:()=>G,subscribe:()=>H,toJSON:()=>J});module.exports=le(ve);function f(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var y=class{constructor(r,t){this._trace=r;this._clock=t;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(r){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=f(r))}};var g=()=>{},m={success:g,fail:g},c={step:()=>m,child:()=>c,tag:g,tags:()=>[],setContext:g,getContext:()=>({}),success:g,fail:g,run:(e,r)=>{try{let t=r();return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}};function U(e){let r=new WeakMap;function t(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(r.has(n))return r.get(n);if(Array.isArray(n)){let s=[];r.set(n,s);for(let o=0;o<n.length;o++)s.push(t(n[o]));return s}let i={};r.set(n,i);for(let s of Object.keys(n))i[s]=t(n[s]);return i}return t(e)}function R(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)R(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&R(t)}return e}function z(e){let r=U(e);return R(r)}var O=new Set;function H(e){return O.add(e),()=>{O.delete(e)}}function ce(e){if(O.size!==0)for(let r of O)try{r(e)}catch{}}var C=class e{constructor(r,t,n,i,s){this._trace=r;this._clock=t;this._idGen=n;this._renderer=i;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return m;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,m;let i={id:this._idGen.generate(),name:r,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:t,children:[]};return this._trace.steps.push(i),new y(i,this._clock)}child(r){if(this._completed)return c;if(this._options.enabled===!1)return c;let t={id:this._idGen.generate(),name:r,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(t,this._clock,this._idGen,this._renderer,{...this._options})}tag(r){this._completed||this._trace.tags.includes(r)||this._trace.tags.push(r)}tags(){return[...this._trace.tags]}setContext(r){this._completed||Object.assign(this._trace.context,r)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(r){this._trace.error=f(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(i=>(n.success(),i),i=>{throw n.fail(i),i})}_finalize(r){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=r;let t=z(this._trace);ce(t),this._renderer.render(t)}};var h=class{generate(){return Math.random().toString(36).slice(2,10)}};var _=class{now(){return Date.now()}};function p(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,i=new WeakSet;function s(o,l){if(typeof o=="function")return`[Fn:${o.name||"?"}]`;if(typeof o=="symbol")return o.toString();if(l>t)return o&&typeof o=="object"?"[...]":o;if(o===null||typeof o!="object")return o;if(o instanceof Map)return`{Map(${o.size})}`;if(o instanceof Set)return`{Set(${o.size})}`;if(o instanceof Date)return o.toISOString();if(o instanceof RegExp)return o.toString();if(i.has(o))return"[Circular]";if(i.add(o),Array.isArray(o))return o.map(u=>s(u,l+1));let T={};for(let u of Object.keys(o)){let $=o[u];T[u]=s($,l+1)}return T}try{let o=JSON.stringify(s(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var de=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,ue=()=>typeof EdgeRuntime<"u",fe=()=>typeof caches<"u"&&"default"in caches,pe=()=>typeof window<"u"||typeof self<"u";function Y(){return de()?"node":ue()||fe()?"edge":pe()?"browser":"node"}var me="\x1B[0m",we="\x1B[32m",ge="\x1B[31m";var xe="\x1B[36m";var Fe="\x1B[2m";function he(){return!(Y()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var _e=he();function v(e,r){return _e?`${r}${e}${me}`:e}function L(e){return v(e,we)}function E(e){return v(e,ge)}function K(e){return v(e,xe)}function P(e){return v(e,Fe)}function Q(e,r){if(r===void 0)return"\u2014";let t=Math.round(r-e);return t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`}function V(e,r,t){let n=" ".repeat(r);t.push(`${n}${K("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${P("data:")} ${p(e.data,{maxLength:80})}`);let i=Q(e.start,e.end);e.status==="success"?t.push(`${n}${L("\u2713")} ${e.name} (${i})`):e.status==="error"?t.push(`${n}${E("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${i})`);for(let s of e.children)V(s,r+1,t)}var S=class{constructor(){this.name="console"}render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",i=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${i} [flow:${r.name}]${n} (${P("tid:")} ${r.id})`);for(let l of r.steps)V(l,1,t);let s=Q(r.start,r.end),o=r.meta?.maxStepsExceeded?` ${E("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${E("failed")} (${s}, error: ${r.error?.message??"Unknown error"})${o}`),console.error(t.join(`
2
+ `))):(t.push(`\u{1F3C1} [flow:${r.name}] ${L("success")} (${s})${o}`),console.log(t.join(`
3
+ `)))}};var D=null;function G(e){D=e}function A(){return D}function X(e){return D??e}var B=new h,M=new _,Te=new S,W=!0;function b(e){W=e}function x(){return W}function Z(e){B=e}function j(e){M=e}function k(e,r){if(!W)return c;let t=r?.sample??1;if(t<1&&Math.random()>t)return c;if(r?.enabled===!1)return c;let n=X(Te),i={id:B.generate(),name:e,state:"running",tags:r?.tags??[],start:M.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new C(i,M,B,n,r??{})}function ye(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function Re(e,r,t){if(r.current&&t===void 0&&!r.current.completed)return r.current.flow;let n=t??e.flowName,i=k(n,{tags:[...e.defaultTags]});return r.current={flow:i,timer:null,lastError:null,completed:!1},i}function Oe(e,r){r.current&&(r.current.timer&&clearTimeout(r.current.timer),r.current.timer=setTimeout(()=>{ee(r)},e.idleTimeout))}function ee(e){!e.current||e.current.completed||(e.current.timer&&(clearTimeout(e.current.timer),e.current.timer=null),e.current.completed=!0,e.current.lastError?e.current.flow.fail(e.current.lastError):e.current.flow.success(),e.current=null)}function N(e,r,t,n,i,s){if(!x())return;let o,l;n===void 0?(o=void 0,l=void 0):ye(n)?i===void 0?(o=void 0,l=n):(o=void 0,l={...n,...i}):(o=n,l=i);let T=l?.flow,u=Re(e,r,T);if(l?.tags)for(let te of l.tags)u.tag(te);let $=s&&s!=="info"?`${t} [${s}]`:t,q=u.step($,o);s==="error"?(q.fail(o),r.current&&(r.current.lastError=o),u.tag("error")):(q.success(),s&&s!=="info"&&u.tag(s)),Oe(e,r)}var w={idleTimeout:100,flowName:"default",defaultTags:[]},F={current:null};function a(e,r,t){N(w,F,e,r,t)}a.info=(e,r,t)=>N(w,F,e,r,t,"info");a.warn=(e,r,t)=>N(w,F,e,r,t,"warn");a.error=(e,r,t)=>N(w,F,e,r,t,"error");a.setIdleTimeout=e=>{w.idleTimeout=e};a.setFlowName=e=>{w.flowName=e};a.setDefaultTags=e=>{w.defaultTags=e};a.activeFlow=()=>F.current?.flow??null;a.flush=()=>{ee(F)};var d=(e,r,t)=>{a(e,r,t)};d.log=a;d.flow=(e,r)=>k(e,r);d.setIdleTimeout=a.setIdleTimeout;d.setFlowName=a.setFlowName;d.setDefaultTags=a.setDefaultTags;d.activeFlow=a.activeFlow;d.flush=a.flush;d.setEnabled=b;d.isEnabled=x;function Ce(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function J(e){let r=Ce(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(re),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function re(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(p(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(re)),r}0&&(module.exports={NOOP_FLOW,NOOP_STEP,better,createFlow,getRenderer,isEnabled,normalizeError,safeSerialize,setClock,setEnabled,setIdGenerator,setRenderer,subscribe,toJSON});
package/dist/index.d.cts CHANGED
@@ -170,17 +170,16 @@ declare function setClock(clock: Clock): void;
170
170
  declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
171
171
 
172
172
  /**
173
- * V3: Zero-Friction Log API
173
+ * V3: Zero-Friction better.log API
174
174
  *
175
175
  * Drop-in console.log replacement with auto-flow management.
176
176
  *
177
177
  * Usage:
178
- * log('message')
179
- * log('message', data)
180
- * log('message', { tags: ['x'] })
181
- * log('message', data, { tags: ['x'], flow: 'custom' })
182
- * log.warn('warning', data)
183
- * log.error('error', error)
178
+ * better.log('message')
179
+ * better.log('message', data)
180
+ * better.log.warn('warning', data)
181
+ * better.log.error('error', error)
182
+ * better.flow('explicit-flow', { tags: ['auth'] })
184
183
  */
185
184
 
186
185
  /** Options for individual log calls */
@@ -190,22 +189,36 @@ interface LogCallOptions {
190
189
  /** Additional tags for this step */
191
190
  tags?: string[];
192
191
  }
193
- /** Public Log API */
194
- interface LogAPI {
195
- (message: string, data?: unknown): void;
196
- (message: string, options?: LogCallOptions): void;
197
- (message: string, data?: unknown, options?: LogCallOptions): void;
198
- info: LogFn;
199
- warn: LogFn;
200
- error: LogFn;
201
- setIdleTimeout(ms: number): void;
202
- setFlowName(name: string): void;
203
- setDefaultTags(tags: string[]): void;
204
- activeFlow(): FlowHandle | null;
205
- flush(): void;
192
+ /** Options for creating an explicit flow via better.flow() */
193
+ interface BetterFlowOptions {
194
+ tags?: string[];
195
+ maxSteps?: number;
196
+ sample?: number;
197
+ initialContext?: Record<string, unknown>;
198
+ }
199
+ declare function logFn(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
200
+ declare namespace logFn {
201
+ var info: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
202
+ var warn: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
203
+ var error: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
204
+ var setIdleTimeout: (ms: number) => void;
205
+ var setFlowName: (name: string) => void;
206
+ var setDefaultTags: (tags: string[]) => void;
207
+ var activeFlow: () => FlowHandle | null;
208
+ var flush: () => void;
206
209
  }
207
- type LogFn = (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
208
- declare const better: LogAPI;
210
+ declare const betterBase: {
211
+ (message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
212
+ log: typeof logFn;
213
+ flow(name: string, options?: BetterFlowOptions): FlowHandle;
214
+ setIdleTimeout: (ms: number) => void;
215
+ setFlowName: (name: string) => void;
216
+ setDefaultTags: (tags: string[]) => void;
217
+ activeFlow: () => FlowHandle | null;
218
+ flush: () => void;
219
+ setEnabled: typeof setEnabled;
220
+ isEnabled: typeof isEnabled;
221
+ };
209
222
 
210
223
  /** Contract for all renderer implementations */
211
224
  interface FlowRenderer {
@@ -233,4 +246,4 @@ declare function toJSON(input: Readonly<FlowTrace> | {
233
246
  trace: Readonly<FlowTrace>;
234
247
  }): string;
235
248
 
236
- export { type LogAPI as BetterLogAPI, type Clock, type FlowListener, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, type LogCallOptions, NOOP_FLOW, NOOP_STEP, type NoOpFlowHandle, type NoOpStepHandle, type NormalizedError, type SerializeOptions, type StepStatus, type StepTrace, better, createFlow, getRenderer, isEnabled, normalizeError, safeSerialize, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
249
+ export { type BetterFlowOptions, type Clock, type FlowListener, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, type LogCallOptions, NOOP_FLOW, NOOP_STEP, type NoOpFlowHandle, type NoOpStepHandle, type NormalizedError, type SerializeOptions, type StepStatus, type StepTrace, betterBase as better, createFlow, getRenderer, isEnabled, normalizeError, safeSerialize, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
package/dist/index.d.ts CHANGED
@@ -170,17 +170,16 @@ declare function setClock(clock: Clock): void;
170
170
  declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
171
171
 
172
172
  /**
173
- * V3: Zero-Friction Log API
173
+ * V3: Zero-Friction better.log API
174
174
  *
175
175
  * Drop-in console.log replacement with auto-flow management.
176
176
  *
177
177
  * Usage:
178
- * log('message')
179
- * log('message', data)
180
- * log('message', { tags: ['x'] })
181
- * log('message', data, { tags: ['x'], flow: 'custom' })
182
- * log.warn('warning', data)
183
- * log.error('error', error)
178
+ * better.log('message')
179
+ * better.log('message', data)
180
+ * better.log.warn('warning', data)
181
+ * better.log.error('error', error)
182
+ * better.flow('explicit-flow', { tags: ['auth'] })
184
183
  */
185
184
 
186
185
  /** Options for individual log calls */
@@ -190,22 +189,36 @@ interface LogCallOptions {
190
189
  /** Additional tags for this step */
191
190
  tags?: string[];
192
191
  }
193
- /** Public Log API */
194
- interface LogAPI {
195
- (message: string, data?: unknown): void;
196
- (message: string, options?: LogCallOptions): void;
197
- (message: string, data?: unknown, options?: LogCallOptions): void;
198
- info: LogFn;
199
- warn: LogFn;
200
- error: LogFn;
201
- setIdleTimeout(ms: number): void;
202
- setFlowName(name: string): void;
203
- setDefaultTags(tags: string[]): void;
204
- activeFlow(): FlowHandle | null;
205
- flush(): void;
192
+ /** Options for creating an explicit flow via better.flow() */
193
+ interface BetterFlowOptions {
194
+ tags?: string[];
195
+ maxSteps?: number;
196
+ sample?: number;
197
+ initialContext?: Record<string, unknown>;
198
+ }
199
+ declare function logFn(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
200
+ declare namespace logFn {
201
+ var info: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
202
+ var warn: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
203
+ var error: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
204
+ var setIdleTimeout: (ms: number) => void;
205
+ var setFlowName: (name: string) => void;
206
+ var setDefaultTags: (tags: string[]) => void;
207
+ var activeFlow: () => FlowHandle | null;
208
+ var flush: () => void;
206
209
  }
207
- type LogFn = (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
208
- declare const better: LogAPI;
210
+ declare const betterBase: {
211
+ (message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
212
+ log: typeof logFn;
213
+ flow(name: string, options?: BetterFlowOptions): FlowHandle;
214
+ setIdleTimeout: (ms: number) => void;
215
+ setFlowName: (name: string) => void;
216
+ setDefaultTags: (tags: string[]) => void;
217
+ activeFlow: () => FlowHandle | null;
218
+ flush: () => void;
219
+ setEnabled: typeof setEnabled;
220
+ isEnabled: typeof isEnabled;
221
+ };
209
222
 
210
223
  /** Contract for all renderer implementations */
211
224
  interface FlowRenderer {
@@ -233,4 +246,4 @@ declare function toJSON(input: Readonly<FlowTrace> | {
233
246
  trace: Readonly<FlowTrace>;
234
247
  }): string;
235
248
 
236
- export { type LogAPI as BetterLogAPI, type Clock, type FlowListener, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, type LogCallOptions, NOOP_FLOW, NOOP_STEP, type NoOpFlowHandle, type NoOpStepHandle, type NormalizedError, type SerializeOptions, type StepStatus, type StepTrace, better, createFlow, getRenderer, isEnabled, normalizeError, safeSerialize, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
249
+ export { type BetterFlowOptions, type Clock, type FlowListener, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, type LogCallOptions, NOOP_FLOW, NOOP_STEP, type NoOpFlowHandle, type NoOpStepHandle, type NormalizedError, type SerializeOptions, type StepStatus, type StepTrace, betterBase as better, createFlow, getRenderer, isEnabled, normalizeError, safeSerialize, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- function u(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var S=class{constructor(t,r){this._trace=t;this._clock=r;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(t){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=u(t))}};var m=()=>{},g={success:m,fail:m},c={step:()=>g,child:()=>c,tag:m,tags:()=>[],setContext:m,getContext:()=>({}),success:m,fail:m,run:(e,t)=>{try{let r=t();return r instanceof Promise?r:Promise.resolve(r)}catch(r){return Promise.reject(r)}}};function D(e){let t=new WeakMap;function r(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(t.has(n))return t.get(n);if(Array.isArray(n)){let s=[];t.set(n,s);for(let o=0;o<n.length;o++)s.push(r(n[o]));return s}let i={};t.set(n,i);for(let s of Object.keys(n))i[s]=r(n[s]);return i}return r(e)}function k(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let t of e)k(t);else for(let t of Object.keys(e)){let r=e[t];r&&typeof r=="object"&&!Object.isFrozen(r)&&k(r)}return e}function $(e){let t=D(e);return k(t)}var T=new Set;function G(e){return T.add(e),()=>{T.delete(e)}}function V(e){if(T.size!==0)for(let t of T)try{t(e)}catch{}}var y=class e{constructor(t,r,n,i,s){this._trace=t;this._clock=r;this._idGen=n;this._renderer=i;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(t,r){if(this._completed)return g;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,g;let i={id:this._idGen.generate(),name:t,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:r,children:[]};return this._trace.steps.push(i),new S(i,this._clock)}child(t){if(this._completed)return c;if(this._options.enabled===!1)return c;let r={id:this._idGen.generate(),name:t,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(r,this._clock,this._idGen,this._renderer,{...this._options})}tag(t){this._completed||this._trace.tags.includes(t)||this._trace.tags.push(t)}tags(){return[...this._trace.tags]}setContext(t){this._completed||Object.assign(this._trace.context,t)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(t){this._trace.error=u(t),this._finalize("error")}run(t,r){if(this._completed)return Promise.resolve(r());let n=this.step(t);return Promise.resolve(r()).then(i=>(n.success(),i),i=>{throw n.fail(i),i})}_finalize(t){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=t;let r=$(this._trace);V(r),this._renderer.render(r)}};var x=class{generate(){return Math.random().toString(36).slice(2,10)}};var h=class{now(){return Date.now()}};function p(e,t){let r=t?.maxDepth??2,n=t?.maxLength??80,i=new WeakSet;function s(o,l){if(typeof o=="function")return`[Fn:${o.name||"?"}]`;if(typeof o=="symbol")return o.toString();if(l>r)return o&&typeof o=="object"?"[...]":o;if(o===null||typeof o!="object")return o;if(o instanceof Map)return`{Map(${o.size})}`;if(o instanceof Set)return`{Set(${o.size})}`;if(o instanceof Date)return o.toISOString();if(o instanceof RegExp)return o.toString();if(i.has(o))return"[Circular]";if(i.add(o),Array.isArray(o))return o.map(f=>s(f,l+1));let _={};for(let f of Object.keys(o)){let v=o[f];_[f]=s(v,l+1)}return _}try{let o=JSON.stringify(s(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var X=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,Z=()=>typeof EdgeRuntime<"u",j=()=>typeof caches<"u"&&"default"in caches,ee=()=>typeof window<"u"||typeof self<"u";function A(){return X()?"node":Z()||j()?"edge":ee()?"browser":"node"}var te="\x1B[0m",re="\x1B[32m",ne="\x1B[31m";var oe="\x1B[36m";var ie="\x1B[2m";function se(){return!(A()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var ae=se();function R(e,t){return ae?`${t}${e}${te}`:e}function E(e){return R(e,re)}function O(e){return R(e,ne)}function M(e){return R(e,oe)}function N(e){return R(e,ie)}function W(e,t){if(t===void 0)return"\u2014";let r=Math.round(t-e);return r>=1e3?`${(r/1e3).toFixed(1)}s`:`${r}ms`}function J(e,t,r){let n=" ".repeat(t);r.push(`${n}${M("\u2192")} ${e.name}`),e.data!==void 0&&r.push(`${n} ${N("data:")} ${p(e.data,{maxLength:80})}`);let i=W(e.start,e.end);e.status==="success"?r.push(`${n}${E("\u2713")} ${e.name} (${i})`):e.status==="error"?r.push(`${n}${O("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):r.push(`${n} ${e.name} (${i})`);for(let s of e.children)J(s,t+1,r)}var F=class{constructor(){this.name="console"}render(t){let r=[],n=t.tags.length>0?` [${t.tags.join(", ")}]`:"",i=t.status==="error"?"\u{1F525}":"\u{1F680}";r.push(`${i} [flow:${t.name}]${n} (${N("tid:")} ${t.id})`);for(let l of t.steps)J(l,1,r);let s=W(t.start,t.end),o=t.meta?.maxStepsExceeded?` ${O("\u26A0\uFE0F maxSteps exceeded")}`:"";t.status==="error"?(r.push(`\u{1F3C1} [flow:${t.name}] ${O("failed")} (${s}, error: ${t.error?.message??"Unknown error"})${o}`),console.error(r.join(`
2
- `))):(r.push(`\u{1F3C1} [flow:${t.name}] ${E("success")} (${s})${o}`),console.log(r.join(`
3
- `)))}};var b=null;function q(e){b=e}function U(){return b}function B(e){return b??e}var L=new x,z=new h,de=new F,I=!0;function fe(e){I=e}function H(){return I}function ue(e){L=e}function pe(e){z=e}function P(e,t){if(!I)return c;let r=t?.sample??1;if(r<1&&Math.random()>r)return c;if(t?.enabled===!1)return c;let n=B(de),i={id:L.generate(),name:e,state:"running",tags:t?.tags??[],start:z.now(),context:t?.initialContext?{...t.initialContext}:{},steps:[]};return new y(i,z,L,n,t??{})}var w={idleTimeout:100,flowName:"default",defaultTags:[]},a=null;function me(e){if(a&&e===void 0&&!a.completed)return a.flow;let t=e??w.flowName,r=P(t,{tags:[...w.defaultTags]});return a={flow:r,timer:null,lastError:null,completed:!1},r}function ge(){a&&(a.timer&&clearTimeout(a.timer),a.timer=setTimeout(()=>{Y()},w.idleTimeout))}function Y(){!a||a.completed||(a.timer&&(clearTimeout(a.timer),a.timer=null),a.completed=!0,a.lastError?a.flow.fail(a.lastError):a.flow.success(),a=null)}function we(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function C(e,t,r,n){if(!H())return;let i,s;t===void 0?(i=void 0,s=void 0):we(t)?r===void 0?(i=void 0,s=t):(i=void 0,s={...t,...r}):(i=t,s=r);let o=s?.flow,l=me(o);if(s?.tags)for(let v of s.tags)l.tag(v);let _=n&&n!=="info"?`${e} [${n}]`:e,f=l.step(_,i);n==="error"?(f.fail(i),a.lastError=i,l.tag("error")):(f.success(),n&&n!=="info"&&l.tag(n)),ge()}var xe=(e,t,r)=>C(e,t,r),d=xe;d.info=(e,t,r)=>C(e,t,r,"info");d.warn=(e,t,r)=>C(e,t,r,"warn");d.error=(e,t,r)=>C(e,t,r,"error");d.setIdleTimeout=e=>{w.idleTimeout=e};d.setFlowName=e=>{w.flowName=e};d.setDefaultTags=e=>{w.defaultTags=e};d.activeFlow=()=>a?.flow??null;d.flush=()=>{Y()};function he(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function K(e){let t=he(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:t.id,name:t.name,...t.parentId!==void 0&&{parentId:t.parentId},state:t.state,...t.status!==void 0&&{status:t.status},tags:t.tags,start:t.start,...t.end!==void 0&&{end:t.end},...t.error!==void 0&&{error:t.error},context:t.context,steps:t.steps.map(Q),...t.meta!==void 0&&{meta:t.meta}}},null,2)}function Q(e){let t={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(t.end=e.end),e.data!==void 0)try{t.data=JSON.parse(p(e.data))}catch{t.data="[Circular or unserializable]"}return e.error!==void 0&&(t.error=e.error),e.children.length>0&&(t.children=e.children.map(Q)),t}export{c as NOOP_FLOW,g as NOOP_STEP,d as better,P as createFlow,U as getRenderer,H as isEnabled,u as normalizeError,p as safeSerialize,pe as setClock,fe as setEnabled,ue as setIdGenerator,q as setRenderer,G as subscribe,K as toJSON};
1
+ function f(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var T=class{constructor(r,t){this._trace=r;this._clock=t;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(r){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=f(r))}};var w=()=>{},g={success:w,fail:w},c={step:()=>g,child:()=>c,tag:w,tags:()=>[],setContext:w,getContext:()=>({}),success:w,fail:w,run:(e,r)=>{try{let t=r();return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}};function B(e){let r=new WeakMap;function t(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(r.has(n))return r.get(n);if(Array.isArray(n)){let s=[];r.set(n,s);for(let o=0;o<n.length;o++)s.push(t(n[o]));return s}let i={};r.set(n,i);for(let s of Object.keys(n))i[s]=t(n[s]);return i}return t(e)}function y(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)y(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&y(t)}return e}function $(e){let r=B(e);return y(r)}var R=new Set;function M(e){return R.add(e),()=>{R.delete(e)}}function ee(e){if(R.size!==0)for(let r of R)try{r(e)}catch{}}var O=class e{constructor(r,t,n,i,s){this._trace=r;this._clock=t;this._idGen=n;this._renderer=i;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return g;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,g;let i={id:this._idGen.generate(),name:r,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:t,children:[]};return this._trace.steps.push(i),new T(i,this._clock)}child(r){if(this._completed)return c;if(this._options.enabled===!1)return c;let t={id:this._idGen.generate(),name:r,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(t,this._clock,this._idGen,this._renderer,{...this._options})}tag(r){this._completed||this._trace.tags.includes(r)||this._trace.tags.push(r)}tags(){return[...this._trace.tags]}setContext(r){this._completed||Object.assign(this._trace.context,r)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(r){this._trace.error=f(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(i=>(n.success(),i),i=>{throw n.fail(i),i})}_finalize(r){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=r;let t=$(this._trace);ee(t),this._renderer.render(t)}};var F=class{generate(){return Math.random().toString(36).slice(2,10)}};var h=class{now(){return Date.now()}};function p(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,i=new WeakSet;function s(o,l){if(typeof o=="function")return`[Fn:${o.name||"?"}]`;if(typeof o=="symbol")return o.toString();if(l>t)return o&&typeof o=="object"?"[...]":o;if(o===null||typeof o!="object")return o;if(o instanceof Map)return`{Map(${o.size})}`;if(o instanceof Set)return`{Set(${o.size})}`;if(o instanceof Date)return o.toISOString();if(o instanceof RegExp)return o.toString();if(i.has(o))return"[Circular]";if(i.add(o),Array.isArray(o))return o.map(u=>s(u,l+1));let k={};for(let u of Object.keys(o)){let N=o[u];k[u]=s(N,l+1)}return k}try{let o=JSON.stringify(s(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var re=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,te=()=>typeof EdgeRuntime<"u",ne=()=>typeof caches<"u"&&"default"in caches,oe=()=>typeof window<"u"||typeof self<"u";function W(){return re()?"node":te()||ne()?"edge":oe()?"browser":"node"}var ie="\x1B[0m",se="\x1B[32m",ae="\x1B[31m";var le="\x1B[36m";var ce="\x1B[2m";function de(){return!(W()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var ue=de();function C(e,r){return ue?`${r}${e}${ie}`:e}function I(e){return C(e,se)}function v(e){return C(e,ae)}function J(e){return C(e,le)}function z(e){return C(e,ce)}function q(e,r){if(r===void 0)return"\u2014";let t=Math.round(r-e);return t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`}function U(e,r,t){let n=" ".repeat(r);t.push(`${n}${J("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${z("data:")} ${p(e.data,{maxLength:80})}`);let i=q(e.start,e.end);e.status==="success"?t.push(`${n}${I("\u2713")} ${e.name} (${i})`):e.status==="error"?t.push(`${n}${v("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${i})`);for(let s of e.children)U(s,r+1,t)}var _=class{constructor(){this.name="console"}render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",i=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${i} [flow:${r.name}]${n} (${z("tid:")} ${r.id})`);for(let l of r.steps)U(l,1,t);let s=q(r.start,r.end),o=r.meta?.maxStepsExceeded?` ${v("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${v("failed")} (${s}, error: ${r.error?.message??"Unknown error"})${o}`),console.error(t.join(`
2
+ `))):(t.push(`\u{1F3C1} [flow:${r.name}] ${I("success")} (${s})${o}`),console.log(t.join(`
3
+ `)))}};var H=null;function Y(e){H=e}function K(){return H}function Q(e){return H??e}var L=new F,P=new h,me=new _,D=!0;function G(e){D=e}function S(){return D}function we(e){L=e}function ge(e){P=e}function E(e,r){if(!D)return c;let t=r?.sample??1;if(t<1&&Math.random()>t)return c;if(r?.enabled===!1)return c;let n=Q(me),i={id:L.generate(),name:e,state:"running",tags:r?.tags??[],start:P.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new O(i,P,L,n,r??{})}function xe(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function Fe(e,r,t){if(r.current&&t===void 0&&!r.current.completed)return r.current.flow;let n=t??e.flowName,i=E(n,{tags:[...e.defaultTags]});return r.current={flow:i,timer:null,lastError:null,completed:!1},i}function he(e,r){r.current&&(r.current.timer&&clearTimeout(r.current.timer),r.current.timer=setTimeout(()=>{V(r)},e.idleTimeout))}function V(e){!e.current||e.current.completed||(e.current.timer&&(clearTimeout(e.current.timer),e.current.timer=null),e.current.completed=!0,e.current.lastError?e.current.flow.fail(e.current.lastError):e.current.flow.success(),e.current=null)}function b(e,r,t,n,i,s){if(!S())return;let o,l;n===void 0?(o=void 0,l=void 0):xe(n)?i===void 0?(o=void 0,l=n):(o=void 0,l={...n,...i}):(o=n,l=i);let k=l?.flow,u=Fe(e,r,k);if(l?.tags)for(let j of l.tags)u.tag(j);let N=s&&s!=="info"?`${t} [${s}]`:t,A=u.step(N,o);s==="error"?(A.fail(o),r.current&&(r.current.lastError=o),u.tag("error")):(A.success(),s&&s!=="info"&&u.tag(s)),he(e,r)}var m={idleTimeout:100,flowName:"default",defaultTags:[]},x={current:null};function a(e,r,t){b(m,x,e,r,t)}a.info=(e,r,t)=>b(m,x,e,r,t,"info");a.warn=(e,r,t)=>b(m,x,e,r,t,"warn");a.error=(e,r,t)=>b(m,x,e,r,t,"error");a.setIdleTimeout=e=>{m.idleTimeout=e};a.setFlowName=e=>{m.flowName=e};a.setDefaultTags=e=>{m.defaultTags=e};a.activeFlow=()=>x.current?.flow??null;a.flush=()=>{V(x)};var d=(e,r,t)=>{a(e,r,t)};d.log=a;d.flow=(e,r)=>E(e,r);d.setIdleTimeout=a.setIdleTimeout;d.setFlowName=a.setFlowName;d.setDefaultTags=a.setDefaultTags;d.activeFlow=a.activeFlow;d.flush=a.flush;d.setEnabled=G;d.isEnabled=S;function _e(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function X(e){let r=_e(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(Z),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function Z(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(p(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(Z)),r}export{c as NOOP_FLOW,g as NOOP_STEP,d as better,E as createFlow,K as getRenderer,S as isEnabled,f as normalizeError,p as safeSerialize,ge as setClock,G as setEnabled,we as setIdGenerator,Y as setRenderer,M as subscribe,X as toJSON};
package/package.json CHANGED
@@ -52,5 +52,5 @@
52
52
  "@typescript-eslint/eslint-plugin": "8.58.0",
53
53
  "@types/node": "20.11.0"
54
54
  },
55
- "version": "0.6.0"
55
+ "version": "0.8.0"
56
56
  }