@lensmcp/react-instrumentation 1.23.4 → 1.23.7

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/index.d.ts CHANGED
@@ -4,10 +4,11 @@ export { tracedUseEffect, tracedUseMemo, tracedUseCallback, } from './lib/traced
4
4
  export { TraceSlot, type TraceSlotProps } from './lib/trace-slot.js';
5
5
  export { withTraceComponent, type WithTraceOptions } from './lib/hoc.js';
6
6
  export { installFlowFetch, type FlowFetchOptions } from './lib/flow-fetch.js';
7
+ export { installRouteFlows, routeNodeId, type RouteFlowEnvironment } from './lib/route-flows.js';
7
8
  export { useComponentIdentity } from './lib/component-identity.js';
8
9
  export type { ComponentIdentity } from './lib/component-identity.js';
9
10
  export { installFiberRenderPublisher, resetFiberRenderPublisherForTests } from './lib/fiber-renders.js';
10
- export { publish, publishLazy, setPublisher, drainQueue, queuedByteCount, runInFlow, currentFlow, activeFlow, extendFlowWindow, beginBackgroundFlow, withFlow, type PublishEnvelope, type PublishCategory, type FlowSpec, } from './lib/publish.js';
11
+ export { publish, publishLazy, setPublisher, drainQueue, queuedByteCount, runInFlow, currentFlow, activeFlow, extendFlowWindow, beginBackgroundFlow, flowForNewActivity, configureAmbientFlows, DEFAULT_AMBIENT_FLOW_MAX_MS, FLOW_ORIGIN_FINGERPRINT_PREFIX, withFlow, type AmbientFlowOptions, type BackgroundFlowOrigin, type PublishEnvelope, type PublishCategory, type FlowSpec, } from './lib/publish.js';
11
12
  export { componentLogicalId, hookLogicalId, slotLogicalId, makeInstanceId, depsHash, resetIdentityState, } from './lib/identity.js';
12
13
  export type { RenderRecord, EffectRecord, HookRecord, SlotChangeRecord, ComponentMountRecord, ReactPublishPayload, } from './lib/types.js';
13
14
  export { default as lensmcpBabelPlugin, type LensmcpBabelOptions } from './lib/babel-plugin.js';
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";export{LensmcpRoot}from"./lib/lensmcp-root.js";export{LensmcpContext,useLensmcpContext}from"./lib/context.js";export{tracedUseEffect,tracedUseMemo,tracedUseCallback}from"./lib/traced-hooks.js";export{TraceSlot}from"./lib/trace-slot.js";export{withTraceComponent}from"./lib/hoc.js";export{installFlowFetch}from"./lib/flow-fetch.js";export{useComponentIdentity}from"./lib/component-identity.js";export{installFiberRenderPublisher,resetFiberRenderPublisherForTests}from"./lib/fiber-renders.js";export{publish,publishLazy,setPublisher,drainQueue,queuedByteCount,runInFlow,currentFlow,activeFlow,extendFlowWindow,beginBackgroundFlow,withFlow}from"./lib/publish.js";export{componentLogicalId,hookLogicalId,slotLogicalId,makeInstanceId,depsHash,resetIdentityState}from"./lib/identity.js";export{default as lensmcpBabelPlugin}from"./lib/babel-plugin.js";
1
+ "use strict";export{LensmcpRoot}from"./lib/lensmcp-root.js";export{LensmcpContext,useLensmcpContext}from"./lib/context.js";export{tracedUseEffect,tracedUseMemo,tracedUseCallback}from"./lib/traced-hooks.js";export{TraceSlot}from"./lib/trace-slot.js";export{withTraceComponent}from"./lib/hoc.js";export{installFlowFetch}from"./lib/flow-fetch.js";export{installRouteFlows,routeNodeId}from"./lib/route-flows.js";export{useComponentIdentity}from"./lib/component-identity.js";export{installFiberRenderPublisher,resetFiberRenderPublisherForTests}from"./lib/fiber-renders.js";export{publish,publishLazy,setPublisher,drainQueue,queuedByteCount,runInFlow,currentFlow,activeFlow,extendFlowWindow,beginBackgroundFlow,flowForNewActivity,configureAmbientFlows,DEFAULT_AMBIENT_FLOW_MAX_MS,FLOW_ORIGIN_FINGERPRINT_PREFIX,withFlow}from"./lib/publish.js";export{componentLogicalId,hookLogicalId,slotLogicalId,makeInstanceId,depsHash,resetIdentityState}from"./lib/identity.js";export{default as lensmcpBabelPlugin}from"./lib/babel-plugin.js";
@@ -15,6 +15,11 @@ export interface FlowFetchOptions {
15
15
  * (an array, or a comma-separated string) before the root module instead.
16
16
  */
17
17
  propagateTo?: string[];
18
+ /**
19
+ * Start a flow for navigation that no interaction caused (back/forward, programmatic, redirects long
20
+ * after load). See `installRouteFlows`. Default `true`; pass `false` to keep route changes flowless.
21
+ */
22
+ routeFlows?: boolean;
18
23
  }
19
24
  /**
20
25
  * `x-lensmcp-origin-node-id` is a SOURCE PATH — `ui:src/features/billing/CardForm.tsx:118:click`.
package/lib/flow-fetch.js CHANGED
@@ -1 +1 @@
1
- "use strict";var v=Object.defineProperty;var f=(t,e)=>v(t,"name",{value:e,configurable:!0});var F=Object.defineProperty,l=f((t,e)=>F(t,"name",{value:e,configurable:!0}),"f");import{activeFlow as N,beginBackgroundFlow as H,extendFlowWindow as $,publish as g}from"./publish.js";export function mayPropagateTo(t,e=[]){const h=typeof location<"u"?location.href:void 0;let u;try{u=new URL(t,h)}catch{return!1}if(typeof location<"u"&&u.origin===location.origin||b(u.hostname))return!0;for(const i of e){let r;try{r=new URL(i).origin}catch{continue}if(r===u.origin)return!0}return!1}f(mayPropagateTo,"mayPropagateTo"),l(mayPropagateTo,"mayPropagateTo");const P=new Set(["localhost","[::1]","::1"]);function b(t){const e=t.toLowerCase();return P.has(e)||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(e)}f(b,"k"),l(b,"isLoopbackHost");function I(){const t=globalThis.__LENSMCP_FLOW_PROPAGATE__;return Array.isArray(t)?t.filter(e=>typeof e=="string"):typeof t=="string"?t.split(",").map(e=>e.trim()).filter(Boolean):[]}f(I,"F"),l(I,"globalPropagateList");function x(){return Date.now().toString(36)+Math.random().toString(36).slice(2,12)}f(x,"S"),l(x,"newRequestId");function p(t){return typeof t=="string"?t:t instanceof URL?t.href:t.url??String(t)}f(p,"$"),l(p,"urlOf");let L=!1;export function installFlowFetch(t={}){const e=globalThis;if(typeof e.fetch!="function")return()=>{};!L&&typeof location<"u"&&typeof document<"u"&&(L=!0,H({originType:"page-load",originNodeId:`page:${location.pathname}`}));const h=t.flowHeader??"x-lensmcp-flow-id",u=t.originHeader??"x-lensmcp-origin-node-id",i=t.requestHeader??"x-request-id",r=t.propagateTo??[],a=e.fetch.bind(globalThis);globalThis.__LENSMCP_FLOW_FETCH__=!0;const s=l(((n,c)=>{const o=N();if(!o?.flowId)return a(n,c);if(!mayPropagateTo(p(n),[...r,...I()])){const T=a(n,c);return w(o,n,c,T,void 0),T}const k=typeof n=="object"&&n!==null&&"headers"in n?n.headers:void 0,d=new Headers(c?.headers??k??void 0);d.has(h)||d.set(h,o.flowId),o.originNodeId&&!d.has(u)&&d.set(u,o.originNodeId);const y=d.get(i)??x();d.has(i)||d.set(i,y);const m=a(n,{...c,headers:d});return w(o,n,c,m,y),m}),"patched");return e.fetch=s,()=>{e.fetch=a}}f(installFlowFetch,"installFlowFetch"),l(installFlowFetch,"installFlowFetch");function _(t){const e=typeof location<"u"?location.href:void 0;try{return new URL(t,e).href}catch{return}}f(_,"N"),l(_,"absoluteHref");function w(t,e,h,u,i){const r=p(e),a=_(r),s=h?.method??(typeof e=="object"&&e!==null&&"method"in e?e.method??"GET":"GET"),n=Date.now(),c={flowId:t.flowId,...t.originType?{originType:t.originType}:{},...t.originNodeId?{originNodeId:t.originNodeId}:{},...i?{requestId:i}:{}};g({source:"react",category:"network",severity:"info",title:`fetch ${s} ${r}`,fingerprint:`flow-fetch:${s}:${r}`,context:c,raw:{kind:"fetch-start",url:r,method:s,...a?{href:a}:{},...i?{requestId:i}:{}}}),u.then(o=>{$(t),g({source:"react",category:"network",severity:o.ok||o.status===304?"info":"error",title:`fetch ${s} ${r} \u2192 ${o.status}${o.statusText?` ${o.statusText}`:""}`,fingerprint:`flow-fetch:${s}:${r}:${o.status}`,context:c,raw:{kind:"fetch-done",url:r,...a?{href:a}:{},...i?{requestId:i}:{},method:s,status:o.status,statusText:o.statusText,ok:o.ok,startedAt:n,durationMs:Date.now()-n}})},o=>{$(t),g({source:"react",category:"network",severity:"error",title:`fetch ${s} ${r} failed`,message:o instanceof Error?o.message:String(o),fingerprint:`flow-fetch:${s}:${r}:error`,context:c,raw:{kind:"fetch-error",url:r,...a?{href:a}:{},...i?{requestId:i}:{},method:s,startedAt:n,durationMs:Date.now()-n}})})}f(w,"p"),l(w,"publishFetchLeg");
1
+ "use strict";var v=Object.defineProperty;var c=(t,e)=>v(t,"name",{value:e,configurable:!0});var N=Object.defineProperty,l=c((t,e)=>N(t,"name",{value:e,configurable:!0}),"f");import{beginBackgroundFlow as H,extendFlowWindow as b,flowForNewActivity as P,publish as p}from"./publish.js";import{installRouteFlows as R}from"./route-flows.js";export function mayPropagateTo(t,e=[]){const d=typeof location<"u"?location.href:void 0;let f;try{f=new URL(t,d)}catch{return!1}if(typeof location<"u"&&f.origin===location.origin||I(f.hostname))return!0;for(const n of e){let r;try{r=new URL(n).origin}catch{continue}if(r===f.origin)return!0}return!1}c(mayPropagateTo,"mayPropagateTo"),l(mayPropagateTo,"mayPropagateTo");const S=new Set(["localhost","[::1]","::1"]);function I(t){const e=t.toLowerCase();return S.has(e)||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(e)}c(I,"S"),l(I,"isLoopbackHost");function x(){const t=globalThis.__LENSMCP_FLOW_PROPAGATE__;return Array.isArray(t)?t.filter(e=>typeof e=="string"):typeof t=="string"?t.split(",").map(e=>e.trim()).filter(Boolean):[]}c(x,"N"),l(x,"globalPropagateList");function F(){return Date.now().toString(36)+Math.random().toString(36).slice(2,12)}c(F,"O"),l(F,"newRequestId");function w(t){return typeof t=="string"?t:t instanceof URL?t.href:t.url??String(t)}c(w,"$"),l(w,"urlOf");let L=!1;export function installFlowFetch(t={}){const e=globalThis;if(typeof e.fetch!="function")return()=>{};!L&&typeof location<"u"&&typeof document<"u"&&(L=!0,H({originType:"page-load",originNodeId:`page:${location.pathname}`}));const d=t.flowHeader??"x-lensmcp-flow-id",f=t.originHeader??"x-lensmcp-origin-node-id",n=t.requestHeader??"x-request-id",r=t.propagateTo??[],s=e.fetch.bind(globalThis);globalThis.__LENSMCP_FLOW_FETCH__=!0;const a=l(((i,o)=>{const g=P();if(!g?.flowId)return s(i,o);if(!mayPropagateTo(w(i),[...r,...x()])){const $=s(i,o);return y(g,i,o,$,void 0),$}const k=typeof i=="object"&&i!==null&&"headers"in i?i.headers:void 0,u=new Headers(o?.headers??k??void 0);u.has(d)||u.set(d,g.flowId),g.originNodeId&&!u.has(f)&&u.set(f,g.originNodeId);const m=u.get(n)??F();u.has(n)||u.set(n,m);const T=s(i,{...o,headers:u});return y(g,i,o,T,m),T}),"patched");e.fetch=a;const h=t.routeFlows===!1?()=>{}:R();return()=>{h(),e.fetch=s}}c(installFlowFetch,"installFlowFetch"),l(installFlowFetch,"installFlowFetch");function _(t){const e=typeof location<"u"?location.href:void 0;try{return new URL(t,e).href}catch{return}}c(_,"R"),l(_,"absoluteHref");function y(t,e,d,f,n){const r=w(e),s=_(r),a=d?.method??(typeof e=="object"&&e!==null&&"method"in e?e.method??"GET":"GET"),h=Date.now(),i={flowId:t.flowId,...t.originType?{originType:t.originType}:{},...t.originNodeId?{originNodeId:t.originNodeId}:{},...n?{requestId:n}:{}};p({source:"react",category:"network",severity:"info",title:`fetch ${a} ${r}`,fingerprint:`flow-fetch:${a}:${r}`,context:i,raw:{kind:"fetch-start",url:r,method:a,...s?{href:s}:{},...n?{requestId:n}:{}}}),f.then(o=>{b(t),p({source:"react",category:"network",severity:o.ok||o.status===304?"info":"error",title:`fetch ${a} ${r} \u2192 ${o.status}${o.statusText?` ${o.statusText}`:""}`,fingerprint:`flow-fetch:${a}:${r}:${o.status}`,context:i,raw:{kind:"fetch-done",url:r,...s?{href:s}:{},...n?{requestId:n}:{},method:a,status:o.status,statusText:o.statusText,ok:o.ok,startedAt:h,durationMs:Date.now()-h}})},o=>{b(t),p({source:"react",category:"network",severity:"error",title:`fetch ${a} ${r} failed`,message:o instanceof Error?o.message:String(o),fingerprint:`flow-fetch:${a}:${r}:error`,context:i,raw:{kind:"fetch-error",url:r,...s?{href:s}:{},...n?{requestId:n}:{},method:a,startedAt:h,durationMs:Date.now()-h}})})}c(y,"b"),l(y,"publishFetchLeg");
package/lib/publish.d.ts CHANGED
@@ -30,6 +30,30 @@ declare global {
30
30
  }
31
31
  /** Test seam: what the queue is currently holding, in estimated bytes. */
32
32
  export declare function queuedByteCount(): number;
33
+ /**
34
+ * AMBIENT flows are the ones nobody clicked: the page load, a route change, a rollover. They exist so
35
+ * the work that follows an event the page cannot attribute still has a trace to join — and the window
36
+ * above is what keeps them alive. Every fetch COMPLETION re-arms it, so a page that polls faster than
37
+ * the window (an interval under four seconds, a long-poll chain, an SSE reconnect loop) re-arms it
38
+ * forever: one flow, no end, every poll appended. A real workspace produced exactly that.
39
+ *
40
+ * So an ambient flow has a MAXIMUM AGE. Past it the window is no longer re-armed for that flow, and the
41
+ * NEXT piece of network activity starts a fresh one (`flowForNewActivity`) that records which flow it
42
+ * continues. A flow started by a user action is untouched: a click's flow stays whatever it has always
43
+ * been, because "what did my click cause" has no natural end the producer can judge.
44
+ *
45
+ * The age is a cap on a flow's length, not on its event count. A page load legitimately carries
46
+ * thousands of events in a few seconds with no causal seam to cut at; only time has one.
47
+ */
48
+ export declare const DEFAULT_AMBIENT_FLOW_MAX_MS = 30000;
49
+ export interface AmbientFlowOptions {
50
+ /**
51
+ * Longest an ambient flow may live before the next network activity rolls it over to a new one.
52
+ * `0`, `Infinity` or `'off'` disables the cap (the pre-cap behaviour). Default 30 000 ms.
53
+ */
54
+ maxLifetimeMs?: number | 'off';
55
+ }
56
+ export declare function configureAmbientFlows(options: AmbientFlowOptions): void;
33
57
  export declare function extendFlowWindow(spec?: FlowSpec): void;
34
58
  /** The flow that owns work happening NOW: sync stack first, else the window. */
35
59
  export declare function activeFlow(): FlowSpec | undefined;
@@ -39,10 +63,30 @@ export declare function activeFlow(): FlowSpec | undefined;
39
63
  * follows inherits the flow) and publishes the origin event that makes the
40
64
  * flow exist in the reducer.
41
65
  */
42
- export declare function beginBackgroundFlow(origin: {
66
+ export declare function beginBackgroundFlow(origin: BackgroundFlowOrigin): FlowSpec;
67
+ /** What starts an ambient flow: the origin type and node, and — for a rollover — the flow it continues. */
68
+ export interface BackgroundFlowOrigin {
43
69
  originType: string;
44
70
  originNodeId?: string;
45
- }): FlowSpec;
71
+ continuesFlowId?: string;
72
+ }
73
+ /**
74
+ * The flow that a piece of network activity issued RIGHT NOW belongs to — which is `activeFlow()`, except
75
+ * that an ambient flow past its age cap is rolled over first: a new background flow starts, with its own
76
+ * `flow-origin`, and the activity belongs to that.
77
+ *
78
+ * Only ambient flows roll over, and only when there is activity to attribute. A flow whose window simply
79
+ * lapsed (an idle page) is not resurrected, and a flow started by a user action is never rolled.
80
+ */
81
+ export declare function flowForNewActivity(): FlowSpec | undefined;
82
+ /**
83
+ * A flow's origin event is the one thing that makes it a flow rather than a fragment, so it must never
84
+ * be the event a flood guard throws away. Every guard on the path to the bus is a per-FINGERPRINT token
85
+ * bucket (burst 30, then 5/s) — and the origin used to share one fingerprint per origin NODE, so the
86
+ * 31st click on a button inside a few seconds lost its origin and its flow arrived headless. Unique per
87
+ * FLOW, the first event of a fingerprint is always admitted, whatever the buckets downstream are doing.
88
+ */
89
+ export declare const FLOW_ORIGIN_FINGERPRINT_PREFIX = "flow-origin:";
46
90
  export declare function setPublisher(fn: PublishFn | undefined): void;
47
91
  /**
48
92
  * Push a flow onto the module-level stack for the duration of `fn`. Any
@@ -85,6 +129,12 @@ export declare function publish(env: PublishEnvelope): void;
85
129
  * clone or walk happens.
86
130
  */
87
131
  export declare function publishLazy(fingerprint: string, build: () => PublishEnvelope): void;
132
+ /**
133
+ * Test hook: forget every flow, window and ambient record, and any configured cap. The flow state is module
134
+ * state by design (it must outlive a render), so a test that exercises window and rollover timing needs a
135
+ * clean slate that does not depend on running in a fresh module registry.
136
+ */
137
+ export declare function resetFlowStateForTests(): void;
88
138
  /** Test hook — only useful in unit tests / smokes. */
89
139
  export declare function drainQueue(): PublishEnvelope[];
90
140
  export {};
package/lib/publish.js CHANGED
@@ -1 +1 @@
1
- "use strict";var k=Object.defineProperty;var n=(e,o)=>k(e,"name",{value:o,configurable:!0});var E=Object.defineProperty,t=n((e,o)=>E(e,"name",{value:o,configurable:!0}),"t");import{createEventFloodGuard as L,estimateJsonBytes as P}from"@lensmcp/protocol-types";const u=[],D=200,T=(()=>{const e=typeof process<"u"?process.env?.LENSMCP_PUBLISH_QUEUE_BYTES:void 0,o=Number(e);return Number.isFinite(o)&&o>0?Math.floor(o):1024*1024})(),g=[];let s=0;function x(e){const o=P(e,T);for(u.push(e),g.push(o),s+=o;u.length>D||s>T&&u.length>1;)u.shift(),s-=g.shift()??0;u.length===0&&(s=0)}n(x,"m"),t(x,"enqueue");function F(){const e=u.shift();return s-=g.shift()??0,u.length===0&&(s=0),e}n(F,"L"),t(F,"dequeue");export function queuedByteCount(){return s}n(queuedByteCount,"queuedByteCount"),t(queuedByteCount,"queuedByteCount");const a=[],I=4e3;let l;export function extendFlowWindow(e){const o=e??currentFlow()??l?.spec;o&&(l={spec:o,until:Date.now()+I})}n(extendFlowWindow,"extendFlowWindow"),t(extendFlowWindow,"extendFlowWindow");function w(){return l&&Date.now()<l.until?l.spec:void 0}n(w,"y"),t(w,"windowedFlow");export function activeFlow(){return currentFlow()??w()}n(activeFlow,"activeFlow"),t(activeFlow,"activeFlow");export function beginBackgroundFlow(e){const o={flowId:y(),originType:e.originType,...e.originNodeId?{originNodeId:e.originNodeId}:{}};return l={spec:o,until:Date.now()+I},publish({source:"react",category:"runtime",severity:"info",title:e.originType,fingerprint:`flow-origin:${e.originNodeId??e.originType}`,raw:{kind:"flow-origin",originNodeId:e.originNodeId}}),o}n(beginBackgroundFlow,"beginBackgroundFlow"),t(beginBackgroundFlow,"beginBackgroundFlow");export function setPublisher(e){if(globalThis.__LENSMCP_PUBLISH__=e,e)for(;u.length;){const o=F();if(o)try{e(o)}catch{}}}n(setPublisher,"setPublisher"),t(setPublisher,"setPublisher");export function runInFlow(e,o){a.push(e),l={spec:e,until:Date.now()+I};try{return o()}finally{a.pop()}}n(runInFlow,"runInFlow"),t(runInFlow,"runInFlow");export function currentFlow(){return a[a.length-1]}n(currentFlow,"currentFlow"),t(currentFlow,"currentFlow");const v=Symbol.for("lensmcp.flowed"),B=2e3,d=new Map;function _(e){if(e==null||typeof e!="object")return;const o=e,i=o.nativeEvent;if(i!=null&&typeof i=="object"){const r=i.isTrusted;if(typeof r=="boolean")return r}if(typeof o.isTrusted=="boolean")return o.isTrusted}n(_,"k"),t(_,"eventTrust");function S(e){if(!(d.size<=64))for(const[o,i]of d)e>=i.until&&d.delete(o)}n(S,"M"),t(S,"pruneInputBursts");export function withFlow(e,o={}){if(typeof e!="function"||e[v])return e;const i=t(()=>{publish({source:"react",category:"runtime",severity:"info",title:o.originType??"user-click",fingerprint:`flow-origin:${o.originNodeId??"unknown"}`,raw:{kind:"flow-origin",originNodeId:o.originNodeId}})},"publishOrigin"),r=t(function(...c){if(o.originType==="input-change"){if(_(c[0])===!1)return currentFlow()==null&&w()!=null&&extendFlowWindow(),e(...c);const N=o.originNodeId??"input",f=Date.now(),p=d.get(N);if(p&&f<p.until)return p.until=f+B,runInFlow(p.spec,()=>e(...c));const b={flowId:y(),originType:"input-change",originNodeId:o.originNodeId};return d.set(N,{spec:b,until:f+B}),S(f),runInFlow(b,()=>(i(),e(...c)))}const m=y();return runInFlow({flowId:m,originType:o.originType??"user-click",originNodeId:o.originNodeId},()=>(i(),e(...c)))},"flowed");return r[v]=!0,r}n(withFlow,"withFlow"),t(withFlow,"withFlow");let h=0;function y(){return h=(h+1)%65535,`flow:${Date.now().toString(36)}:${h.toString(36)}`}n(y,"a"),t(y,"newFlowId");export function publish(e){const o=currentFlow()??(e.context?.flowId?void 0:w()),i=o==null?e:{...e,context:{...o.flowId?{flowId:o.flowId}:{},...o.originType?{originType:o.originType}:{},...o.originNodeId?{originNodeId:o.originNodeId}:{},...o.causedByNodeId?{causedByNodeId:o.causedByNodeId}:{},...e.context}},r=globalThis.__LENSMCP_PUBLISH__;if(r)try{r(i);return}catch{}x(i)}n(publish,"publish"),t(publish,"publish");const M=L({burst:30,perSec:5});export function publishLazy(e,o){const i=M.admit(e);if(!i.ok)return;const r=o();if(i.suppressed>0&&r.raw!==null&&typeof r.raw=="object"){publish({...r,raw:{...r.raw,floodSuppressed:i.suppressed}});return}publish(r)}n(publishLazy,"publishLazy"),t(publishLazy,"publishLazy");export function drainQueue(){return g.length=0,s=0,u.splice(0,u.length)}n(drainQueue,"drainQueue"),t(drainQueue,"drainQueue");
1
+ "use strict";var C=Object.defineProperty;var t=(e,o)=>C(e,"name",{value:o,configurable:!0});var O=Object.defineProperty,n=t((e,o)=>O(e,"name",{value:o,configurable:!0}),"t");import{createEventFloodGuard as W,estimateJsonBytes as U}from"@lensmcp/protocol-types";const u=[],$=200,v=(()=>{const e=typeof process<"u"?process.env?.LENSMCP_PUBLISH_QUEUE_BYTES:void 0,o=Number(e);return Number.isFinite(o)&&o>0?Math.floor(o):1024*1024})(),I=[];let s=0;function M(e){const o=U(e,v);for(u.push(e),I.push(o),s+=o;u.length>$||s>v&&u.length>1;)u.shift(),s-=I.shift()??0;u.length===0&&(s=0)}t(M,"W"),n(M,"enqueue");function S(){const e=u.shift();return s-=I.shift()??0,u.length===0&&(s=0),e}t(S,"k"),n(S,"dequeue");export function queuedByteCount(){return s}t(queuedByteCount,"queuedByteCount"),n(queuedByteCount,"queuedByteCount");const p=[],F=4e3;let c;export const DEFAULT_AMBIENT_FLOW_MAX_MS=3e4;let y;function N(e){if(e==="off"||e==="Infinity")return 1/0;const o=typeof e=="string"&&e.trim()!==""?Number(e):e;if(!(typeof o!="number"||Number.isNaN(o)||o<0))return o===0||o===1/0?1/0:o}t(N,"_"),n(N,"parseMaxLifetime");function L(){if(y!==void 0)return y;const e=N(globalThis.__LENSMCP_AMBIENT_FLOW_MAX_MS__);return e!==void 0?e:N(typeof process<"u"?process.env?.LENSMCP_AMBIENT_FLOW_MAX_MS:void 0)??DEFAULT_AMBIENT_FLOW_MAX_MS}t(L,"D"),n(L,"ambientMaxMs");export function configureAmbientFlows(e){y=e.maxLifetimeMs===void 0?void 0:N(e.maxLifetimeMs)}t(configureAmbientFlows,"configureAmbientFlows"),n(configureAmbientFlows,"configureAmbientFlows");const l=new Map,X=16;function E(e){for(l.set(e,{bornAt:Date.now()});l.size>X;)l.delete(l.keys().next().value)}t(E,"C"),n(E,"trackAmbient");function b(e){const o=l.get(e.flowId);if(!o)return!1;const i=L();return i!==1/0&&Date.now()-o.bornAt>=i}t(b,"x"),n(b,"ambientExhausted");export function extendFlowWindow(e){const o=e??currentFlow()??c?.spec;o&&(b(o)||(c={spec:o,until:Date.now()+F}))}t(extendFlowWindow,"extendFlowWindow"),n(extendFlowWindow,"extendFlowWindow");function a(){return c&&Date.now()<c.until?c.spec:void 0}t(a,"g"),n(a,"windowedFlow");export function activeFlow(){return currentFlow()??a()}t(activeFlow,"activeFlow"),n(activeFlow,"activeFlow");export function beginBackgroundFlow(e){const o={flowId:h(),originType:e.originType,...e.originNodeId?{originNodeId:e.originNodeId}:{}};return c={spec:o,until:Date.now()+F},E(o.flowId),publish({source:"react",category:"runtime",severity:"info",title:e.originType,fingerprint:_(e.originNodeId??e.originType,o.flowId),raw:{kind:"flow-origin",originNodeId:e.originNodeId,...e.continuesFlowId?{continuesFlowId:e.continuesFlowId}:{}}}),o}t(beginBackgroundFlow,"beginBackgroundFlow"),n(beginBackgroundFlow,"beginBackgroundFlow");export function flowForNewActivity(){const e=currentFlow();if(e)return e;const o=a();if(o)return b(o)?beginBackgroundFlow({originType:"timer",originNodeId:typeof location<"u"?`ambient:${location.pathname}`:"ambient",continuesFlowId:o.flowId}):o}t(flowForNewActivity,"flowForNewActivity"),n(flowForNewActivity,"flowForNewActivity");export const FLOW_ORIGIN_FINGERPRINT_PREFIX="flow-origin:";function _(e,o){return`${FLOW_ORIGIN_FINGERPRINT_PREFIX}${e}:${o}`}t(_,"M"),n(_,"originFingerprint");export function setPublisher(e){if(globalThis.__LENSMCP_PUBLISH__=e,e)for(;u.length;){const o=S();if(o)try{e(o)}catch{}}}t(setPublisher,"setPublisher"),n(setPublisher,"setPublisher");export function runInFlow(e,o){p.push(e),c={spec:e,until:Date.now()+F};try{return o()}finally{p.pop()}}t(runInFlow,"runInFlow"),n(runInFlow,"runInFlow");export function currentFlow(){return p[p.length-1]}t(currentFlow,"currentFlow"),n(currentFlow,"currentFlow");const B=Symbol.for("lensmcp.flowed"),A=2e3,f=new Map;function P(e){if(e==null||typeof e!="object")return;const o=e,i=o.nativeEvent;if(i!=null&&typeof i=="object"){const r=i.isTrusted;if(typeof r=="boolean")return r}if(typeof o.isTrusted=="boolean")return o.isTrusted}t(P,"v"),n(P,"eventTrust");function k(e){if(!(f.size<=64))for(const[o,i]of f)e>=i.until&&f.delete(o)}t(k,"X"),n(k,"pruneInputBursts");export function withFlow(e,o={}){if(typeof e!="function"||e[B])return e;const i=n(()=>{publish({source:"react",category:"runtime",severity:"info",title:o.originType??"user-click",fingerprint:_(o.originNodeId??"unknown",currentFlow()?.flowId??""),raw:{kind:"flow-origin",originNodeId:o.originNodeId}})},"publishOrigin"),r=n(function(...d){if(o.originType==="input-change"){if(P(d[0])===!1)return currentFlow()==null&&a()!=null&&extendFlowWindow(),e(...d);const T=o.originNodeId??"input",w=Date.now(),g=f.get(T);if(g&&w<g.until)return g.until=w+A,runInFlow(g.spec,()=>e(...d));const x={flowId:h(),originType:"input-change",originNodeId:o.originNodeId};return f.set(T,{spec:x,until:w+A}),k(w),runInFlow(x,()=>(i(),e(...d)))}const D=h();return runInFlow({flowId:D,originType:o.originType??"user-click",originNodeId:o.originNodeId},()=>(i(),e(...d)))},"flowed");return r[B]=!0,r}t(withFlow,"withFlow"),n(withFlow,"withFlow");let m=0;function h(){return m=(m+1)%65535,`flow:${Date.now().toString(36)}:${m.toString(36)}`}t(h,"E"),n(h,"newFlowId");export function publish(e){const o=currentFlow()??(e.context?.flowId?void 0:a()),i=o==null?e:{...e,context:{...o.flowId?{flowId:o.flowId}:{},...o.originType?{originType:o.originType}:{},...o.originNodeId?{originNodeId:o.originNodeId}:{},...o.causedByNodeId?{causedByNodeId:o.causedByNodeId}:{},...e.context}},r=globalThis.__LENSMCP_PUBLISH__;if(r)try{r(i);return}catch{}M(i)}t(publish,"publish"),n(publish,"publish");const j=W({burst:30,perSec:5});export function publishLazy(e,o){const i=e.startsWith(FLOW_ORIGIN_FINGERPRINT_PREFIX)?{ok:!0,suppressed:0}:j.admit(e);if(!i.ok)return;const r=o();if(i.suppressed>0&&r.raw!==null&&typeof r.raw=="object"){publish({...r,raw:{...r.raw,floodSuppressed:i.suppressed}});return}publish(r)}t(publishLazy,"publishLazy"),n(publishLazy,"publishLazy");export function resetFlowStateForTests(){p.length=0,c=void 0,l.clear(),f.clear(),y=void 0}t(resetFlowStateForTests,"resetFlowStateForTests"),n(resetFlowStateForTests,"resetFlowStateForTests");export function drainQueue(){return I.length=0,s=0,u.splice(0,u.length)}t(drainQueue,"drainQueue"),n(drainQueue,"drainQueue");
@@ -0,0 +1,10 @@
1
+ /** The slice of `window` this needs — named so a test (or a non-browser host) can provide its own. */
2
+ export interface RouteFlowEnvironment {
3
+ history: Pick<History, 'pushState' | 'replaceState'>;
4
+ location: Pick<Location, 'pathname'>;
5
+ addEventListener(type: 'popstate', listener: () => void): void;
6
+ removeEventListener(type: 'popstate', listener: () => void): void;
7
+ }
8
+ /** The node id of a route flow: `route:/companies/42/staff`. Pathname only, clipped. */
9
+ export declare function routeNodeId(pathname: string): string;
10
+ export declare function installRouteFlows(env?: RouteFlowEnvironment | undefined): () => void;
@@ -0,0 +1 @@
1
+ "use strict";var g=Object.defineProperty;var n=(e,o)=>g(e,"name",{value:o,configurable:!0});var S=Object.defineProperty,t=n((e,o)=>S(e,"name",{value:o,configurable:!0}),"e");import{activeFlow as m,beginBackgroundFlow as E}from"./publish.js";const f=200;function y(){const e=globalThis.window;if(!(!e?.history||!e.location||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function"))return e}n(y,"E"),t(y,"browserEnvironment");export function routeNodeId(e){return`route:${e.length>f?e.slice(0,f):e}`}n(routeNodeId,"routeNodeId"),t(routeNodeId,"routeNodeId");let a;export function installRouteFlows(e=y()){if(!e)return()=>{};if(a)return()=>{};let o=e.location.pathname,s=!0;const i=t(()=>{if(!s)return;const r=e.location.pathname;r!==o&&(o=r,!m()&&E({originType:"route-load",originNodeId:routeNodeId(r)}))},"navigated"),u=t(r=>t(function(...v){const w=r.apply(this,v);try{i()}catch{}return w},"wrapped"),"wrap"),c=e.history.pushState,l=e.history.replaceState,p=u(c),h=u(l);e.history.pushState=p,e.history.replaceState=h,e.addEventListener("popstate",i);const d=t(()=>{s=!1,e.removeEventListener("popstate",i),e.history.pushState===p&&(e.history.pushState=c),e.history.replaceState===h&&(e.history.replaceState=l),a=void 0},"uninstall");return a=d,d}n(installRouteFlows,"installRouteFlows"),t(installRouteFlows,"installRouteFlows");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/react-instrumentation",
3
- "version": "1.23.4",
3
+ "version": "1.23.7",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@lensmcp/protocol-types": "1.23.4",
22
+ "@lensmcp/protocol-types": "1.23.7",
23
23
  "tslib": "^2.3.0"
24
24
  },
25
25
  "peerDependencies": {