@better-logger/core 0.1.0 → 0.2.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/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";var y=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var q=(e,t)=>{for(var r in t)y(e,r,{get:t[r],enumerable:!0})},J=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of M(t))!W.call(e,s)&&s!==r&&y(e,s,{get:()=>t[s],enumerable:!(o=A(t,s))||o.enumerable});return e};var U=e=>J(y({},"__esModule",{value:!0}),e);var ie={};q(ie,{createFlow:()=>G,subscribe:()=>T,toJSON:()=>E});module.exports=U(ie);function l(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 x=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=l(t))}};var c=()=>{},u={success:c,fail:c},a={step:()=>u,child:()=>a,tag:c,tags:()=>[],setContext:c,getContext:()=>({}),success:c,fail:c};function N(e){let t=new WeakMap;function r(o){if(o===null||typeof o!="object")return o;if(o instanceof Date)return new Date(o.getTime());if(o instanceof RegExp)return new RegExp(o.source,o.flags);if(t.has(o))return t.get(o);if(Array.isArray(o)){let i=[];t.set(o,i);for(let n=0;n<o.length;n++)i.push(r(o[n]));return i}let s={};t.set(o,s);for(let i of Object.keys(o))s[i]=r(o[i]);return s}return r(e)}function h(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let t of e)h(t);else for(let t of Object.keys(e)){let r=e[t];r&&typeof r=="object"&&!Object.isFrozen(r)&&h(r)}return e}function R(e){let t=N(e);return h(t)}var _=new Set;function T(e){return _.add(e),()=>{_.delete(e)}}function Y(e){if(_.size!==0)for(let t of _)try{t(e)}catch{}}var S=class e{constructor(t,r,o,s,i){this._trace=t;this._clock=r;this._idGen=o;this._renderer=s;this._options=i;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(t,r){if(this._completed)return u;let o=this._options.maxSteps;if(o!==void 0&&this._trace.steps.length>=o)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,u;let s={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(s),new x(s,this._clock)}child(t){if(this._completed)return a;if(this._options.enabled===!1)return a;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=l(t),this._finalize("error")}_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=R(this._trace);Y(r),this._renderer.render(r)}};var f=class{generate(){return Math.random().toString(36).slice(2,10)}};var p=class{now(){return Date.now()}};function m(e,t){let r=t?.maxDepth??2,o=t?.maxLength??80,s=new WeakSet;function i(n,d){if(typeof n=="function")return`[Fn:${n.name||"?"}]`;if(typeof n=="symbol")return n.toString();if(d>r)return n&&typeof n=="object"?"[...]":n;if(n===null||typeof n!="object")return n;if(n instanceof Map)return`{Map(${n.size})}`;if(n instanceof Set)return`{Set(${n.size})}`;if(n instanceof Date)return n.toISOString();if(n instanceof RegExp)return n.toString();if(s.has(n))return"[Circular]";if(s.add(n),Array.isArray(n))return n.map(w=>i(w,d+1));let v={};for(let w of Object.keys(n)){let P=n[w];v[w]=i(P,d+1)}return v}try{let n=JSON.stringify(i(e,0));return n&&n.length>o?n.slice(0,o)+"\u2026":n}catch{return"[Unserializable]"}}var B=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,K=()=>typeof EdgeRuntime<"u",Q=()=>typeof caches<"u"&&"default"in caches,V=()=>typeof window<"u"||typeof self<"u";function C(){return B()?"node":K()||Q()?"edge":V()?"browser":"node"}var X="\x1B[0m",Z="\x1B[32m",j="\x1B[31m";var ee="\x1B[36m";var te="\x1B[2m";function re(){return!(C()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var ne=re();function F(e,t){return ne?`${t}${e}${X}`:e}function O(e){return F(e,Z)}function k(e){return F(e,j)}function z(e){return F(e,ee)}function $(e){return F(e,te)}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 H(e,t,r){let o=" ".repeat(t);r.push(`${o}${z("\u2192")} ${e.name}`),e.data!==void 0&&r.push(`${o} ${$("data:")} ${m(e.data,{maxLength:80})}`);let s=b(e.start,e.end);e.status==="success"?r.push(`${o}${O("\u2713")} ${e.name} (${s})`):e.status==="error"?r.push(`${o}${k("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):r.push(`${o} ${e.name} (${s})`);for(let i of e.children)H(i,t+1,r)}var g=class{render(t){let r=[],o=t.tags.length>0?` [${t.tags.join(", ")}]`:"",s=t.status==="error"?"\u{1F525}":"\u{1F680}";r.push(`${s} [flow:${t.name}]${o} (${$("tid:")} ${t.id})`);for(let d of t.steps)H(d,1,r);let i=b(t.start,t.end),n=t.meta?.maxStepsExceeded?` ${k("\u26A0\uFE0F maxSteps exceeded")}`:"";t.status==="error"?(r.push(`\u{1F3C1} [flow:${t.name}] ${k("failed")} (${i}, error: ${t.error?.message??"Unknown error"})${n}`),console.error(r.join(`
2
- `))):(r.push(`\u{1F3C1} [flow:${t.name}] ${O("success")} (${i})${n}`),console.log(r.join(`
3
- `)))}};var L=new f,D=new p,oe=new g;function G(e,t){if(t?.enabled===!1)return a;let r={id:L.generate(),name:e,state:"running",tags:t?.tags??[],start:D.now(),context:{},steps:[]};return new S(r,D,L,oe,t??{})}function se(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function E(e){let t=se(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(I),...t.meta!==void 0&&{meta:t.meta}}},null,2)}function I(e){let t={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};return e.end!==void 0&&(t.end=e.end),e.data!==void 0&&(t.data=m(e.data)),e.error!==void 0&&(t.error=e.error),e.children.length>0&&(t.children=e.children.map(I)),t}0&&(module.exports={createFlow,subscribe,toJSON});
1
+ "use strict";var R=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Z=(e,r)=>{for(var t in r)R(e,t,{get:r[t],enumerable:!0})},j=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of V(r))!X.call(e,s)&&s!==t&&R(e,s,{get:()=>r[s],enumerable:!(n=Q(r,s))||n.enumerable});return e};var ee=e=>j(R({},"__esModule",{value:!0}),e);var xe={};Z(xe,{NOOP_FLOW:()=>a,NOOP_STEP:()=>c,createFlow:()=>Y,getRenderer:()=>E,isEnabled:()=>q,setClock:()=>U,setEnabled:()=>W,setIdGenerator:()=>J,setRenderer:()=>v,subscribe:()=>T,toJSON:()=>P});module.exports=ee(xe);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 x=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=u(r))}};var d=()=>{},c={success:d,fail:d},a={step:()=>c,child:()=>a,tag:d,tags:()=>[],setContext:d,getContext:()=>({}),success:d,fail:d,run:(e,r)=>r()};function D(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 i=[];r.set(n,i);for(let o=0;o<n.length;o++)i.push(t(n[o]));return i}let s={};r.set(n,s);for(let i of Object.keys(n))s[i]=t(n[i]);return s}return t(e)}function h(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)h(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&h(t)}return e}function y(e){let r=D(e);return h(r)}var _=new Set;function T(e){return _.add(e),()=>{_.delete(e)}}function re(e){if(_.size!==0)for(let r of _)try{r(e)}catch{}}var S=class e{constructor(r,t,n,s,i){this._trace=r;this._clock=t;this._idGen=n;this._renderer=s;this._options=i;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return c;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,c;let s={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(s),new x(s,this._clock)}child(r){if(this._completed)return a;if(this._options.enabled===!1)return a;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=u(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(s=>(n.success(),s),s=>{throw n.fail(s),s})}_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=y(this._trace);re(t),this._renderer.render(t)}};var f=class{generate(){return Math.random().toString(36).slice(2,10)}};var p=class{now(){return Date.now()}};function m(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,s=new WeakSet;function i(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(s.has(o))return"[Circular]";if(s.add(o),Array.isArray(o))return o.map(g=>i(g,l+1));let H={};for(let g of Object.keys(o)){let K=o[g];H[g]=i(K,l+1)}return H}try{let o=JSON.stringify(i(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var te=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,ne=()=>typeof EdgeRuntime<"u",oe=()=>typeof caches<"u"&&"default"in caches,se=()=>typeof window<"u"||typeof self<"u";function G(){return te()?"node":ne()||oe()?"edge":se()?"browser":"node"}var ie="\x1B[0m",ae="\x1B[32m",ce="\x1B[31m";var de="\x1B[36m";var le="\x1B[2m";function ue(){return!(G()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var fe=ue();function k(e,r){return fe?`${r}${e}${ie}`:e}function O(e){return k(e,ae)}function F(e){return k(e,ce)}function I(e){return k(e,de)}function C(e){return k(e,le)}function L(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 A(e,r,t){let n=" ".repeat(r);t.push(`${n}${I("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${C("data:")} ${m(e.data,{maxLength:80})}`);let s=L(e.start,e.end);e.status==="success"?t.push(`${n}${O("\u2713")} ${e.name} (${s})`):e.status==="error"?t.push(`${n}${F("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${s})`);for(let i of e.children)A(i,r+1,t)}var w=class{render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",s=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${s} [flow:${r.name}]${n} (${C("tid:")} ${r.id})`);for(let l of r.steps)A(l,1,t);let i=L(r.start,r.end),o=r.meta?.maxStepsExceeded?` ${F("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${F("failed")} (${i}, error: ${r.error?.message??"Unknown error"})${o}`),console.error(t.join(`
2
+ `))):(t.push(`\u{1F3C1} [flow:${r.name}] ${O("success")} (${i})${o}`),console.log(t.join(`
3
+ `)))}};var $=null;function v(e){$=e}function E(){return $}function M(e){return $??e}var b=new f,N=new p,we=new w,z=!0;function W(e){z=e}function q(){return z}function J(e){b=e}function U(e){N=e}function Y(e,r){if(!z)return a;let t=r?.sample??1;if(t<1&&Math.random()>t)return a;if(r?.enabled===!1)return a;let n=M(we),s={id:b.generate(),name:e,state:"running",tags:r?.tags??[],start:N.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new S(s,N,b,n,r??{})}function ge(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function P(e){let r=ge(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(B),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function B(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};return e.end!==void 0&&(r.end=e.end),e.data!==void 0&&(r.data=m(e.data)),e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(B)),r}0&&(module.exports={NOOP_FLOW,NOOP_STEP,createFlow,getRenderer,isEnabled,setClock,setEnabled,setIdGenerator,setRenderer,subscribe,toJSON});
package/dist/index.d.cts CHANGED
@@ -42,9 +42,26 @@ interface StepTrace {
42
42
  }
43
43
  /** Options for creating a flow */
44
44
  interface FlowOptions {
45
+ /** Enable/disable this flow. Default: true. If false, all operations are no-ops */
45
46
  enabled?: boolean;
47
+ /** Indexing labels for grouping/filtering */
46
48
  tags?: string[];
49
+ /** Safety limit for step count (default: unlimited) */
47
50
  maxSteps?: number;
51
+ /** Sampling rate (0.0 to 1.0). Default: 1.0 (all flows logged). V2 */
52
+ sample?: number;
53
+ /** Initial context to seed the flow with. V2 */
54
+ initialContext?: Record<string, unknown>;
55
+ }
56
+
57
+ /** Contract for clock implementations */
58
+ interface Clock {
59
+ now(): number;
60
+ }
61
+
62
+ /** Contract for ID generation strategies */
63
+ interface IdGenerator {
64
+ generate(): string;
48
65
  }
49
66
 
50
67
  /** Public StepHandle interface */
@@ -54,8 +71,8 @@ interface StepHandle {
54
71
  }
55
72
 
56
73
  /** Public FlowHandle interface */
57
- interface FlowHandle {
58
- step(name: string, data?: unknown): StepHandle;
74
+ interface FlowHandle<Data = unknown> {
75
+ step(name: string, data?: Data): StepHandle;
59
76
  child(name: string): FlowHandle;
60
77
  tag(tag: string): void;
61
78
  tags(): string[];
@@ -63,6 +80,8 @@ interface FlowHandle {
63
80
  getContext(): Record<string, unknown>;
64
81
  success(): void;
65
82
  fail(error?: unknown): void;
83
+ /** V2: Async boundary helper — creates a step, executes fn, auto-completes */
84
+ run<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
66
85
  }
67
86
  /** Listener type for subscribe */
68
87
  type FlowListener = (flow: Readonly<FlowTrace>) => void;
@@ -72,14 +91,82 @@ type FlowListener = (flow: Readonly<FlowTrace>) => void;
72
91
  */
73
92
  declare function subscribe(listener: FlowListener): () => void;
74
93
 
94
+ /**
95
+ * NoOp StepHandle — reused across all disabled/guarded paths.
96
+ * Type matches FlowHandle's StepHandle exactly.
97
+ */
98
+ interface NoOpStepHandle {
99
+ success(): void;
100
+ fail(error?: unknown): void;
101
+ }
102
+ /**
103
+ * NoOp FlowHandle — reused across all disabled/guarded paths.
104
+ * Type signature matches FlowHandle interface exactly.
105
+ */
106
+ interface NoOpFlowHandle {
107
+ step(name: string, data?: unknown): NoOpStepHandle;
108
+ child(name: string): NoOpFlowHandle;
109
+ tag(name: string): void;
110
+ tags(): string[];
111
+ setContext(context: Record<string, unknown>): void;
112
+ getContext(): Record<string, unknown>;
113
+ success(): void;
114
+ fail(error?: unknown): void;
115
+ run<T>(name: string, fn: () => Promise<T>): Promise<T>;
116
+ }
117
+ declare const NOOP_STEP: NoOpStepHandle;
118
+ declare const NOOP_FLOW: NoOpFlowHandle;
119
+
120
+ /**
121
+ * Set the global enabled state for all new flows. V2 feature.
122
+ * Affects only flows created after this call — in-flight flows retain their state.
123
+ *
124
+ * @param enabled - true to enable, false to disable all new flows
125
+ */
126
+ declare function setEnabled(enabled: boolean): void;
127
+ /**
128
+ * Get the current global enabled state. V2 feature.
129
+ */
130
+ declare function isEnabled(): boolean;
131
+ /**
132
+ * Override the default ID generator. V2 feature.
133
+ * Affects only flows created after this call.
134
+ *
135
+ * @param idGen - The ID generator to use
136
+ */
137
+ declare function setIdGenerator(idGen: IdGenerator): void;
138
+ /**
139
+ * Override the default clock. V2 feature.
140
+ * Affects only flows created after this call.
141
+ *
142
+ * @param clock - The clock to use
143
+ */
144
+ declare function setClock(clock: Clock): void;
75
145
  /**
76
146
  * Create a new execution flow with a human-readable name and optional configuration.
77
147
  *
78
148
  * @param name - Human-readable flow name
79
- * @param options - Optional configuration (enabled, tags, maxSteps)
149
+ * @param options - Optional configuration (enabled, tags, maxSteps, sample, initialContext)
80
150
  * @returns FlowHandle for interacting with the flow
81
151
  */
82
- declare function createFlow(name: string, options?: FlowOptions): FlowHandle;
152
+ declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
153
+
154
+ /** Contract for all renderer implementations */
155
+ interface FlowRenderer {
156
+ /** Render a frozen snapshot of a completed flow */
157
+ render(snapshot: Readonly<FlowTrace>): void;
158
+ /** Optional name for debugging/identification — V2 */
159
+ name?: string;
160
+ }
161
+ /**
162
+ * Set the global renderer. V2 feature.
163
+ * @param renderer - The renderer to use for all new flows
164
+ */
165
+ declare function setRenderer(renderer: FlowRenderer): void;
166
+ /**
167
+ * Get the current global renderer, or null if none set. V2 feature.
168
+ */
169
+ declare function getRenderer(): FlowRenderer | null;
83
170
 
84
171
  /**
85
172
  * Serialize a FlowTrace to versioned JSON string.
@@ -90,4 +177,4 @@ declare function toJSON(input: Readonly<FlowTrace> | {
90
177
  trace: Readonly<FlowTrace>;
91
178
  }): string;
92
179
 
93
- export { type FlowOptions, type FlowState, type FlowStatus, type FlowTrace, type NormalizedError, type StepStatus, type StepTrace, createFlow, subscribe, toJSON };
180
+ export { type Clock, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, NOOP_FLOW, NOOP_STEP, type NormalizedError, type StepStatus, type StepTrace, createFlow, getRenderer, isEnabled, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
package/dist/index.d.ts CHANGED
@@ -42,9 +42,26 @@ interface StepTrace {
42
42
  }
43
43
  /** Options for creating a flow */
44
44
  interface FlowOptions {
45
+ /** Enable/disable this flow. Default: true. If false, all operations are no-ops */
45
46
  enabled?: boolean;
47
+ /** Indexing labels for grouping/filtering */
46
48
  tags?: string[];
49
+ /** Safety limit for step count (default: unlimited) */
47
50
  maxSteps?: number;
51
+ /** Sampling rate (0.0 to 1.0). Default: 1.0 (all flows logged). V2 */
52
+ sample?: number;
53
+ /** Initial context to seed the flow with. V2 */
54
+ initialContext?: Record<string, unknown>;
55
+ }
56
+
57
+ /** Contract for clock implementations */
58
+ interface Clock {
59
+ now(): number;
60
+ }
61
+
62
+ /** Contract for ID generation strategies */
63
+ interface IdGenerator {
64
+ generate(): string;
48
65
  }
49
66
 
50
67
  /** Public StepHandle interface */
@@ -54,8 +71,8 @@ interface StepHandle {
54
71
  }
55
72
 
56
73
  /** Public FlowHandle interface */
57
- interface FlowHandle {
58
- step(name: string, data?: unknown): StepHandle;
74
+ interface FlowHandle<Data = unknown> {
75
+ step(name: string, data?: Data): StepHandle;
59
76
  child(name: string): FlowHandle;
60
77
  tag(tag: string): void;
61
78
  tags(): string[];
@@ -63,6 +80,8 @@ interface FlowHandle {
63
80
  getContext(): Record<string, unknown>;
64
81
  success(): void;
65
82
  fail(error?: unknown): void;
83
+ /** V2: Async boundary helper — creates a step, executes fn, auto-completes */
84
+ run<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
66
85
  }
67
86
  /** Listener type for subscribe */
68
87
  type FlowListener = (flow: Readonly<FlowTrace>) => void;
@@ -72,14 +91,82 @@ type FlowListener = (flow: Readonly<FlowTrace>) => void;
72
91
  */
73
92
  declare function subscribe(listener: FlowListener): () => void;
74
93
 
94
+ /**
95
+ * NoOp StepHandle — reused across all disabled/guarded paths.
96
+ * Type matches FlowHandle's StepHandle exactly.
97
+ */
98
+ interface NoOpStepHandle {
99
+ success(): void;
100
+ fail(error?: unknown): void;
101
+ }
102
+ /**
103
+ * NoOp FlowHandle — reused across all disabled/guarded paths.
104
+ * Type signature matches FlowHandle interface exactly.
105
+ */
106
+ interface NoOpFlowHandle {
107
+ step(name: string, data?: unknown): NoOpStepHandle;
108
+ child(name: string): NoOpFlowHandle;
109
+ tag(name: string): void;
110
+ tags(): string[];
111
+ setContext(context: Record<string, unknown>): void;
112
+ getContext(): Record<string, unknown>;
113
+ success(): void;
114
+ fail(error?: unknown): void;
115
+ run<T>(name: string, fn: () => Promise<T>): Promise<T>;
116
+ }
117
+ declare const NOOP_STEP: NoOpStepHandle;
118
+ declare const NOOP_FLOW: NoOpFlowHandle;
119
+
120
+ /**
121
+ * Set the global enabled state for all new flows. V2 feature.
122
+ * Affects only flows created after this call — in-flight flows retain their state.
123
+ *
124
+ * @param enabled - true to enable, false to disable all new flows
125
+ */
126
+ declare function setEnabled(enabled: boolean): void;
127
+ /**
128
+ * Get the current global enabled state. V2 feature.
129
+ */
130
+ declare function isEnabled(): boolean;
131
+ /**
132
+ * Override the default ID generator. V2 feature.
133
+ * Affects only flows created after this call.
134
+ *
135
+ * @param idGen - The ID generator to use
136
+ */
137
+ declare function setIdGenerator(idGen: IdGenerator): void;
138
+ /**
139
+ * Override the default clock. V2 feature.
140
+ * Affects only flows created after this call.
141
+ *
142
+ * @param clock - The clock to use
143
+ */
144
+ declare function setClock(clock: Clock): void;
75
145
  /**
76
146
  * Create a new execution flow with a human-readable name and optional configuration.
77
147
  *
78
148
  * @param name - Human-readable flow name
79
- * @param options - Optional configuration (enabled, tags, maxSteps)
149
+ * @param options - Optional configuration (enabled, tags, maxSteps, sample, initialContext)
80
150
  * @returns FlowHandle for interacting with the flow
81
151
  */
82
- declare function createFlow(name: string, options?: FlowOptions): FlowHandle;
152
+ declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
153
+
154
+ /** Contract for all renderer implementations */
155
+ interface FlowRenderer {
156
+ /** Render a frozen snapshot of a completed flow */
157
+ render(snapshot: Readonly<FlowTrace>): void;
158
+ /** Optional name for debugging/identification — V2 */
159
+ name?: string;
160
+ }
161
+ /**
162
+ * Set the global renderer. V2 feature.
163
+ * @param renderer - The renderer to use for all new flows
164
+ */
165
+ declare function setRenderer(renderer: FlowRenderer): void;
166
+ /**
167
+ * Get the current global renderer, or null if none set. V2 feature.
168
+ */
169
+ declare function getRenderer(): FlowRenderer | null;
83
170
 
84
171
  /**
85
172
  * Serialize a FlowTrace to versioned JSON string.
@@ -90,4 +177,4 @@ declare function toJSON(input: Readonly<FlowTrace> | {
90
177
  trace: Readonly<FlowTrace>;
91
178
  }): string;
92
179
 
93
- export { type FlowOptions, type FlowState, type FlowStatus, type FlowTrace, type NormalizedError, type StepStatus, type StepTrace, createFlow, subscribe, toJSON };
180
+ export { type Clock, type FlowOptions, type FlowRenderer, type FlowState, type FlowStatus, type FlowTrace, type IdGenerator, NOOP_FLOW, NOOP_STEP, type NormalizedError, type StepStatus, type StepTrace, createFlow, getRenderer, isEnabled, setClock, setEnabled, setIdGenerator, setRenderer, subscribe, toJSON };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- function l(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 x=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=l(t))}};var c=()=>{},u={success:c,fail:c},a={step:()=>u,child:()=>a,tag:c,tags:()=>[],setContext:c,getContext:()=>({}),success:c,fail:c};function $(e){let t=new WeakMap;function r(o){if(o===null||typeof o!="object")return o;if(o instanceof Date)return new Date(o.getTime());if(o instanceof RegExp)return new RegExp(o.source,o.flags);if(t.has(o))return t.get(o);if(Array.isArray(o)){let s=[];t.set(o,s);for(let n=0;n<o.length;n++)s.push(r(o[n]));return s}let i={};t.set(o,i);for(let s of Object.keys(o))i[s]=r(o[s]);return i}return r(e)}function h(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let t of e)h(t);else for(let t of Object.keys(e)){let r=e[t];r&&typeof r=="object"&&!Object.isFrozen(r)&&h(r)}return e}function y(e){let t=$(e);return h(t)}var _=new Set;function E(e){return _.add(e),()=>{_.delete(e)}}function I(e){if(_.size!==0)for(let t of _)try{t(e)}catch{}}var S=class e{constructor(t,r,o,i,s){this._trace=t;this._clock=r;this._idGen=o;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 u;let o=this._options.maxSteps;if(o!==void 0&&this._trace.steps.length>=o)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,u;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 x(i,this._clock)}child(t){if(this._completed)return a;if(this._options.enabled===!1)return a;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=l(t),this._finalize("error")}_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=y(this._trace);I(r),this._renderer.render(r)}};var f=class{generate(){return Math.random().toString(36).slice(2,10)}};var p=class{now(){return Date.now()}};function m(e,t){let r=t?.maxDepth??2,o=t?.maxLength??80,i=new WeakSet;function s(n,d){if(typeof n=="function")return`[Fn:${n.name||"?"}]`;if(typeof n=="symbol")return n.toString();if(d>r)return n&&typeof n=="object"?"[...]":n;if(n===null||typeof n!="object")return n;if(n instanceof Map)return`{Map(${n.size})}`;if(n instanceof Set)return`{Set(${n.size})}`;if(n instanceof Date)return n.toISOString();if(n instanceof RegExp)return n.toString();if(i.has(n))return"[Circular]";if(i.add(n),Array.isArray(n))return n.map(w=>s(w,d+1));let O={};for(let w of Object.keys(n)){let G=n[w];O[w]=s(G,d+1)}return O}try{let n=JSON.stringify(s(e,0));return n&&n.length>o?n.slice(0,o)+"\u2026":n}catch{return"[Unserializable]"}}var P=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,A=()=>typeof EdgeRuntime<"u",M=()=>typeof caches<"u"&&"default"in caches,W=()=>typeof window<"u"||typeof self<"u";function v(){return P()?"node":A()||M()?"edge":W()?"browser":"node"}var q="\x1B[0m",J="\x1B[32m",U="\x1B[31m";var Y="\x1B[36m";var B="\x1B[2m";function K(){return!(v()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var Q=K();function F(e,t){return Q?`${t}${e}${q}`:e}function R(e){return F(e,J)}function k(e){return F(e,U)}function N(e){return F(e,Y)}function T(e){return F(e,B)}function C(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 z(e,t,r){let o=" ".repeat(t);r.push(`${o}${N("\u2192")} ${e.name}`),e.data!==void 0&&r.push(`${o} ${T("data:")} ${m(e.data,{maxLength:80})}`);let i=C(e.start,e.end);e.status==="success"?r.push(`${o}${R("\u2713")} ${e.name} (${i})`):e.status==="error"?r.push(`${o}${k("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):r.push(`${o} ${e.name} (${i})`);for(let s of e.children)z(s,t+1,r)}var g=class{render(t){let r=[],o=t.tags.length>0?` [${t.tags.join(", ")}]`:"",i=t.status==="error"?"\u{1F525}":"\u{1F680}";r.push(`${i} [flow:${t.name}]${o} (${T("tid:")} ${t.id})`);for(let d of t.steps)z(d,1,r);let s=C(t.start,t.end),n=t.meta?.maxStepsExceeded?` ${k("\u26A0\uFE0F maxSteps exceeded")}`:"";t.status==="error"?(r.push(`\u{1F3C1} [flow:${t.name}] ${k("failed")} (${s}, error: ${t.error?.message??"Unknown error"})${n}`),console.error(r.join(`
2
- `))):(r.push(`\u{1F3C1} [flow:${t.name}] ${R("success")} (${s})${n}`),console.log(r.join(`
3
- `)))}};var b=new f,H=new p,V=new g;function X(e,t){if(t?.enabled===!1)return a;let r={id:b.generate(),name:e,state:"running",tags:t?.tags??[],start:H.now(),context:{},steps:[]};return new S(r,H,b,V,t??{})}function Z(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function L(e){let t=Z(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(D),...t.meta!==void 0&&{meta:t.meta}}},null,2)}function D(e){let t={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};return e.end!==void 0&&(t.end=e.end),e.data!==void 0&&(t.data=m(e.data)),e.error!==void 0&&(t.error=e.error),e.children.length>0&&(t.children=e.children.map(D)),t}export{X as createFlow,E as subscribe,L as toJSON};
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 x=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=u(r))}};var c=()=>{},d={success:c,fail:c},a={step:()=>d,child:()=>a,tag:c,tags:()=>[],setContext:c,getContext:()=>({}),success:c,fail:c,run:(e,r)=>r()};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 i=[];r.set(n,i);for(let o=0;o<n.length;o++)i.push(t(n[o]));return i}let s={};r.set(n,s);for(let i of Object.keys(n))s[i]=t(n[i]);return s}return t(e)}function h(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)h(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&h(t)}return e}function R(e){let r=b(e);return h(r)}var _=new Set;function N(e){return _.add(e),()=>{_.delete(e)}}function q(e){if(_.size!==0)for(let r of _)try{r(e)}catch{}}var S=class e{constructor(r,t,n,s,i){this._trace=r;this._clock=t;this._idGen=n;this._renderer=s;this._options=i;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return d;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,d;let s={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(s),new x(s,this._clock)}child(r){if(this._completed)return a;if(this._options.enabled===!1)return a;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=u(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(s=>(n.success(),s),s=>{throw n.fail(s),s})}_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=R(this._trace);q(t),this._renderer.render(t)}};var f=class{generate(){return Math.random().toString(36).slice(2,10)}};var p=class{now(){return Date.now()}};function m(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,s=new WeakSet;function i(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(s.has(o))return"[Circular]";if(s.add(o),Array.isArray(o))return o.map(g=>i(g,l+1));let E={};for(let g of Object.keys(o)){let W=o[g];E[g]=i(W,l+1)}return E}try{let o=JSON.stringify(i(e,0));return o&&o.length>n?o.slice(0,n)+"\u2026":o}catch{return"[Unserializable]"}}var J=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,U=()=>typeof EdgeRuntime<"u",Y=()=>typeof caches<"u"&&"default"in caches,B=()=>typeof window<"u"||typeof self<"u";function z(){return J()?"node":U()||Y()?"edge":B()?"browser":"node"}var K="\x1B[0m",Q="\x1B[32m",V="\x1B[31m";var X="\x1B[36m";var Z="\x1B[2m";function j(){return!(z()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var ee=j();function k(e,r){return ee?`${r}${e}${K}`:e}function y(e){return k(e,Q)}function F(e){return k(e,V)}function P(e){return k(e,X)}function T(e){return k(e,Z)}function H(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 D(e,r,t){let n=" ".repeat(r);t.push(`${n}${P("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${T("data:")} ${m(e.data,{maxLength:80})}`);let s=H(e.start,e.end);e.status==="success"?t.push(`${n}${y("\u2713")} ${e.name} (${s})`):e.status==="error"?t.push(`${n}${F("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${s})`);for(let i of e.children)D(i,r+1,t)}var w=class{render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",s=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${s} [flow:${r.name}]${n} (${T("tid:")} ${r.id})`);for(let l of r.steps)D(l,1,t);let i=H(r.start,r.end),o=r.meta?.maxStepsExceeded?` ${F("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${F("failed")} (${i}, error: ${r.error?.message??"Unknown error"})${o}`),console.error(t.join(`
2
+ `))):(t.push(`\u{1F3C1} [flow:${r.name}] ${y("success")} (${i})${o}`),console.log(t.join(`
3
+ `)))}};var O=null;function G(e){O=e}function I(){return O}function L(e){return O??e}var C=new f,$=new p,ne=new w,v=!0;function oe(e){v=e}function se(){return v}function ie(e){C=e}function ae(e){$=e}function ce(e,r){if(!v)return a;let t=r?.sample??1;if(t<1&&Math.random()>t)return a;if(r?.enabled===!1)return a;let n=L(ne),s={id:C.generate(),name:e,state:"running",tags:r?.tags??[],start:$.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new S(s,$,C,n,r??{})}function de(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function A(e){let r=de(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(M),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function M(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};return e.end!==void 0&&(r.end=e.end),e.data!==void 0&&(r.data=m(e.data)),e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(M)),r}export{a as NOOP_FLOW,d as NOOP_STEP,ce as createFlow,I as getRenderer,se as isEnabled,ae as setClock,oe as setEnabled,ie as setIdGenerator,G as setRenderer,N as subscribe,A as toJSON};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-logger/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Execution flow debugger for modern apps. Zero deps. Works everywhere. <10KB.",
5
5
  "license": "MIT",
6
6
  "repository": {