@lensmcp/react-instrumentation 1.0.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.
@@ -0,0 +1,15 @@
1
+ export interface LensmcpIdentityContext {
2
+ rendererId: string;
3
+ page?: string;
4
+ route?: string;
5
+ /** Optional override — when set, hook helpers tag their events with
6
+ * this component instance instead of falling back to a synthetic id. */
7
+ componentInstanceId?: string;
8
+ /** Stable component logicalId (file + name), if known. */
9
+ componentLogicalId?: string;
10
+ /** Optional parent render id (set by LensmcpRoot's Profiler onRender). */
11
+ lastRenderId?: string;
12
+ }
13
+ export declare const LensmcpContext: import("react").Context<LensmcpIdentityContext>;
14
+ export declare function useLensmcpContext(): LensmcpIdentityContext;
15
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/lib/context.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;6EACyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,eAAO,MAAM,cAAc,iDAAoD,CAAC;AAEhF,wBAAgB,iBAAiB,IAAI,sBAAsB,CAE1D"}
package/lib/context.js ADDED
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'react';
2
+ const defaultCtx = { rendererId: 'lensmcp-root' };
3
+ export const LensmcpContext = createContext(defaultCtx);
4
+ export function useLensmcpContext() {
5
+ return useContext(LensmcpContext);
6
+ }
@@ -0,0 +1,8 @@
1
+ export interface FlowFetchOptions {
2
+ /** Header carrying the flow id. Default `x-lensmcp-flow-id`. */
3
+ flowHeader?: string;
4
+ /** Header carrying the origin node id. Default `x-lensmcp-origin-node-id`. */
5
+ originHeader?: string;
6
+ }
7
+ export declare function installFlowFetch(options?: FlowFetchOptions): () => void;
8
+ //# sourceMappingURL=flow-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-fetch.d.ts","sourceRoot":"","sources":["../../src/lib/flow-fetch.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAWD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,IAAI,CA2C3E"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Flow-header propagation across the network boundary. When a request is
3
+ * made while a flow is active (inside `runInFlow`/`withFlow`), inject
4
+ * `x-lensmcp-flow-id` + `x-lensmcp-origin-node-id` headers so the backend
5
+ * (`TraceInterceptor` in `@lensmcp/nest-instrumentation`) stamps its
6
+ * server-request + span + db-query events with the SAME flowId. That is
7
+ * what lets `story.compile` and `graph.*` correlate a single user action
8
+ * all the way from the click to the DB query.
9
+ *
10
+ * Works in the browser and in Node (both have a global `fetch`). The
11
+ * header is read at *call time* (synchronous), so a `fetch()` invoked
12
+ * inside the synchronous portion of a flow is tagged even though the
13
+ * awaited continuation runs after the flow stack unwinds.
14
+ */
15
+ import { activeFlow, beginBackgroundFlow, extendFlowWindow, publish } from './publish.js';
16
+ /**
17
+ * Monkeypatch `globalThis.fetch` to inject flow headers. Returns an
18
+ * uninstall function that restores the original. No-op (returns a no-op
19
+ * uninstaller) if there's no global fetch.
20
+ */
21
+ let pageLoadFlowStarted = false;
22
+ export function installFlowFetch(options = {}) {
23
+ const g = globalThis;
24
+ if (typeof g.fetch !== 'function')
25
+ return () => undefined;
26
+ // installFlowFetch runs at ENTRY-MODULE eval (the babel transform injects
27
+ // it before the root render) — earlier than any mount effect. That makes
28
+ // it the one reliable place to declare the PAGE LOAD itself a flow, so the
29
+ // boot-time fetches (/me, list queries…) and the renders they trigger all
30
+ // stitch into one trace. A refresh without this produced NO flow at all.
31
+ if (!pageLoadFlowStarted && typeof location !== 'undefined' && typeof document !== 'undefined') {
32
+ pageLoadFlowStarted = true;
33
+ beginBackgroundFlow({ originType: 'page-load', originNodeId: `page:${location.pathname}` });
34
+ }
35
+ const flowHeader = options.flowHeader ?? 'x-lensmcp-flow-id';
36
+ const originHeader = options.originHeader ?? 'x-lensmcp-origin-node-id';
37
+ const original = g.fetch.bind(globalThis);
38
+ const patched = ((input, init) => {
39
+ // Sync stack (a wrapped handler) OR the causality window (page-load,
40
+ // post-response continuations) — both mean this fetch belongs to a flow.
41
+ const flow = activeFlow();
42
+ if (!flow?.flowId)
43
+ return original(input, init);
44
+ // Merge onto any caller-supplied headers (+ a Request's own headers).
45
+ const requestHeaders = typeof input === 'object' && input !== null && 'headers' in input
46
+ ? input.headers
47
+ : undefined;
48
+ const headers = new Headers(init?.headers ?? requestHeaders ?? undefined);
49
+ if (!headers.has(flowHeader))
50
+ headers.set(flowHeader, flow.flowId);
51
+ if (flow.originNodeId && !headers.has(originHeader)) {
52
+ headers.set(originHeader, flow.originNodeId);
53
+ }
54
+ const result = original(input, { ...init, headers });
55
+ publishFetchLeg(flow, input, init, result);
56
+ return result;
57
+ });
58
+ g.fetch = patched;
59
+ return () => {
60
+ g.fetch = original;
61
+ };
62
+ }
63
+ /**
64
+ * Publish the flow's network leg: a start event (synchronous — stamped from
65
+ * the flow stack) and a completion event (asynchronous — the flow has
66
+ * unwound by then, so its context is attached explicitly). Together with
67
+ * the backend's server-request event (same flowId via the header) this is
68
+ * what lets a story read click → fetch → server → DB.
69
+ */
70
+ function publishFetchLeg(flow, input, init, result) {
71
+ const url = typeof input === 'string'
72
+ ? input
73
+ : input instanceof URL
74
+ ? input.href
75
+ : (input.url ?? String(input));
76
+ const method = init?.method ??
77
+ (typeof input === 'object' && input !== null && 'method' in input
78
+ ? (input.method ?? 'GET')
79
+ : 'GET');
80
+ const startedAt = Date.now();
81
+ const flowContext = {
82
+ flowId: flow.flowId,
83
+ ...(flow.originType ? { originType: flow.originType } : {}),
84
+ ...(flow.originNodeId ? { originNodeId: flow.originNodeId } : {}),
85
+ };
86
+ publish({
87
+ source: 'react',
88
+ category: 'network',
89
+ severity: 'info',
90
+ title: `fetch ${method} ${url}`,
91
+ fingerprint: `flow-fetch:${method}:${url}`,
92
+ raw: { kind: 'fetch-start', url, method },
93
+ });
94
+ // Observe on a branch — never alter the caller's promise chain.
95
+ void result.then((res) => {
96
+ // Re-open the causality window: the state update + re-render that
97
+ // consume this response land in the next ticks and belong to this flow.
98
+ extendFlowWindow(flow);
99
+ publish({
100
+ source: 'react',
101
+ category: 'network',
102
+ severity: res.ok ? 'info' : 'warning',
103
+ title: `fetch ${method} ${url} → ${res.status}`,
104
+ fingerprint: `flow-fetch:${method}:${url}`,
105
+ context: flowContext,
106
+ raw: { kind: 'fetch-done', url, method, status: res.status, ok: res.ok, startedAt, durationMs: Date.now() - startedAt },
107
+ });
108
+ }, (err) => {
109
+ extendFlowWindow(flow);
110
+ publish({
111
+ source: 'react',
112
+ category: 'network',
113
+ severity: 'error',
114
+ title: `fetch ${method} ${url} failed`,
115
+ message: err instanceof Error ? err.message : String(err),
116
+ fingerprint: `flow-fetch:${method}:${url}`,
117
+ context: flowContext,
118
+ raw: { kind: 'fetch-error', url, method, startedAt, durationMs: Date.now() - startedAt },
119
+ });
120
+ });
121
+ }
package/lib/hoc.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type ComponentType } from 'react';
2
+ export interface WithTraceOptions {
3
+ componentName?: string;
4
+ /** Source file path — set this for projects that can't enable the JSX transform. */
5
+ source?: string;
6
+ }
7
+ /**
8
+ * `withTraceComponent(MyComponent)` — opt-in tracer for projects that
9
+ * can't enable the global Babel transform. Wraps the component in a
10
+ * Profiler and emits a `render` event on every commit.
11
+ */
12
+ export declare function withTraceComponent<P extends object>(Wrapped: ComponentType<P>, opts?: WithTraceOptions): ComponentType<P>;
13
+ //# sourceMappingURL=hoc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hoc.d.ts","sourceRoot":"","sources":["../../src/lib/hoc.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAGnB,MAAM,OAAO,CAAC;AAKf,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EACjD,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,IAAI,GAAE,gBAAqB,GAC1B,aAAa,CAAC,CAAC,CAAC,CA+ClB"}
package/lib/hoc.js ADDED
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Profiler, } from 'react';
3
+ import { publish } from './publish.js';
4
+ import { makeInstanceId } from './identity.js';
5
+ /**
6
+ * `withTraceComponent(MyComponent)` — opt-in tracer for projects that
7
+ * can't enable the global Babel transform. Wraps the component in a
8
+ * Profiler and emits a `render` event on every commit.
9
+ */
10
+ export function withTraceComponent(Wrapped, opts = {}) {
11
+ const name = opts.componentName ?? Wrapped.displayName ?? Wrapped.name ?? 'TracedComponent';
12
+ const file = opts.source ?? '(unknown)';
13
+ const logicalId = `react:component:${file}:${name}`;
14
+ function Traced(props) {
15
+ let lastRenderId;
16
+ const onRender = (_id, phase, actualDuration, baseDuration, startTime, commitTime) => {
17
+ const { instanceId } = makeInstanceId(logicalId);
18
+ const record = {
19
+ id: instanceId,
20
+ parentRenderId: lastRenderId,
21
+ componentInstanceId: instanceId,
22
+ componentLogicalId: logicalId,
23
+ componentName: name,
24
+ phase: phase === 'mount' ? 'mount' : 'update',
25
+ actualDurationMs: actualDuration,
26
+ baseDurationMs: baseDuration,
27
+ startTime,
28
+ commitTime,
29
+ why: [{ type: 'force', reason: 'withTraceComponent' }],
30
+ };
31
+ lastRenderId = instanceId;
32
+ publish({
33
+ source: 'react',
34
+ category: 'render',
35
+ severity: 'info',
36
+ title: `render ${name} (${phase})`,
37
+ fingerprint: `react-render:${logicalId}`,
38
+ raw: { kind: 'render', render: record },
39
+ });
40
+ };
41
+ return (_jsx(Profiler, { id: logicalId, onRender: onRender, children: _jsx(Wrapped, { ...props }) }));
42
+ }
43
+ Traced.displayName = `Traced(${name})`;
44
+ return Traced;
45
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Identity helpers — produce stable `logicalId` and per-mount
3
+ * `instanceId` values for components, hooks, and slots. Mirrors the
4
+ * shapes documented in `planning/02-data-model.md#logicalid`.
5
+ */
6
+ export declare function nextGeneration(logicalId: string): number;
7
+ export declare function shortHash(): string;
8
+ export declare function componentLogicalId(file: string, name: string): string;
9
+ export declare function hookLogicalId(file: string, componentName: string, hookName: string, index: number): string;
10
+ export declare function slotLogicalId(file: string, line: number, kind: string): string;
11
+ export declare function makeInstanceId(logicalId: string): {
12
+ instanceId: string;
13
+ generation: number;
14
+ };
15
+ /**
16
+ * Cheap deterministic hash over an array of dependency values. Used by
17
+ * the traced hooks to compute `depsHash` so the reducer can decide
18
+ * whether a hook re-ran because of a dep change.
19
+ */
20
+ export declare function depsHash(deps: readonly unknown[] | undefined): string;
21
+ /** Test hook to reset generation counters between smoke iterations. */
22
+ export declare function resetIdentityState(): void;
23
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/lib/identity.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,SAAS,IAAI,MAAM,CAGlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,MAAM,CAER;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAMA;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAAG,MAAM,CAUrE;AAoBD,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Identity helpers — produce stable `logicalId` and per-mount
3
+ * `instanceId` values for components, hooks, and slots. Mirrors the
4
+ * shapes documented in `planning/02-data-model.md#logicalid`.
5
+ */
6
+ const generations = new Map();
7
+ let entropy = 0;
8
+ export function nextGeneration(logicalId) {
9
+ const g = (generations.get(logicalId) ?? 0) + 1;
10
+ generations.set(logicalId, g);
11
+ return g;
12
+ }
13
+ export function shortHash() {
14
+ entropy = (entropy + 1) % 0xffffffff;
15
+ return ((Date.now() & 0xfff) ^ entropy).toString(36).slice(-4);
16
+ }
17
+ export function componentLogicalId(file, name) {
18
+ return `react:component:${file}:${name}`;
19
+ }
20
+ export function hookLogicalId(file, componentName, hookName, index) {
21
+ return `react:hook:${file}:${componentName}:${hookName}:${index}`;
22
+ }
23
+ export function slotLogicalId(file, line, kind) {
24
+ return `react:slot:${file}:${line}:${kind}`;
25
+ }
26
+ export function makeInstanceId(logicalId) {
27
+ const generation = nextGeneration(logicalId);
28
+ return {
29
+ instanceId: `${logicalId}#${generation}#${shortHash()}`,
30
+ generation,
31
+ };
32
+ }
33
+ /**
34
+ * Cheap deterministic hash over an array of dependency values. Used by
35
+ * the traced hooks to compute `depsHash` so the reducer can decide
36
+ * whether a hook re-ran because of a dep change.
37
+ */
38
+ export function depsHash(deps) {
39
+ if (!deps)
40
+ return 'no-deps';
41
+ let h = 5381;
42
+ for (const dep of deps) {
43
+ const repr = canonical(dep);
44
+ for (let i = 0; i < repr.length; i++) {
45
+ h = (h * 33) ^ repr.charCodeAt(i);
46
+ }
47
+ }
48
+ return (h >>> 0).toString(36);
49
+ }
50
+ function canonical(v) {
51
+ if (v === null)
52
+ return 'null';
53
+ if (v === undefined)
54
+ return 'undefined';
55
+ const t = typeof v;
56
+ if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') {
57
+ return `${t}:${String(v)}`;
58
+ }
59
+ if (t === 'function') {
60
+ const name = v.name ?? 'anonymous';
61
+ return `fn:${name}:${v.toString().length}`;
62
+ }
63
+ try {
64
+ return `obj:${JSON.stringify(v)}`;
65
+ }
66
+ catch {
67
+ return `obj:circular`;
68
+ }
69
+ }
70
+ /** Test hook to reset generation counters between smoke iterations. */
71
+ export function resetIdentityState() {
72
+ generations.clear();
73
+ entropy = 0;
74
+ }
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface LensmcpRootProps {
3
+ /** Stable id for this renderer (defaults to "lensmcp-root"). */
4
+ rendererId?: string;
5
+ /** Logical page/route label. The reducer uses this to group renders. */
6
+ page?: string;
7
+ route?: string;
8
+ children: ReactNode;
9
+ }
10
+ /**
11
+ * Wraps the React tree in a `<Profiler>` and a context provider so the
12
+ * hook helpers can tag their events with the right renderer / page /
13
+ * route. Drop-in:
14
+ *
15
+ * ReactDOM.createRoot(el).render(
16
+ * <LensmcpRoot page="home" route="/">
17
+ * <App />
18
+ * </LensmcpRoot>
19
+ * );
20
+ */
21
+ export declare function LensmcpRoot(props: LensmcpRootProps): ReactNode;
22
+ //# sourceMappingURL=lensmcp-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lensmcp-root.d.ts","sourceRoot":"","sources":["../../src/lib/lensmcp-root.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAMf,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAuD9D"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Profiler, useMemo, useRef, } from 'react';
3
+ import { LensmcpContext } from './context.js';
4
+ import { publish } from './publish.js';
5
+ import { makeInstanceId } from './identity.js';
6
+ /**
7
+ * Wraps the React tree in a `<Profiler>` and a context provider so the
8
+ * hook helpers can tag their events with the right renderer / page /
9
+ * route. Drop-in:
10
+ *
11
+ * ReactDOM.createRoot(el).render(
12
+ * <LensmcpRoot page="home" route="/">
13
+ * <App />
14
+ * </LensmcpRoot>
15
+ * );
16
+ */
17
+ export function LensmcpRoot(props) {
18
+ const rendererId = props.rendererId ?? 'lensmcp-root';
19
+ const ctxValue = useMemo(() => ({ rendererId, page: props.page, route: props.route }), [rendererId, props.page, props.route]);
20
+ const renderIdsRef = useRef(new Map());
21
+ const lastRenderIdRef = useRef(undefined);
22
+ const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
23
+ const logicalId = `render:${rendererId}:${id}`;
24
+ const { instanceId } = makeInstanceId(logicalId);
25
+ const renderRecord = {
26
+ id: instanceId,
27
+ parentRenderId: lastRenderIdRef.current,
28
+ componentInstanceId: instanceId,
29
+ componentLogicalId: logicalId,
30
+ componentName: id,
31
+ page: props.page,
32
+ route: props.route,
33
+ rendererId,
34
+ phase: phase === 'mount' ? 'mount' : 'update',
35
+ actualDurationMs: actualDuration,
36
+ baseDurationMs: baseDuration,
37
+ startTime,
38
+ commitTime,
39
+ why: [{ type: 'force', reason: 'profiler-onrender' }],
40
+ };
41
+ lastRenderIdRef.current = instanceId;
42
+ renderIdsRef.current.set(id, instanceId);
43
+ publish({
44
+ source: 'react',
45
+ category: 'render',
46
+ severity: 'info',
47
+ title: `render ${id} (${phase})`,
48
+ fingerprint: `react-render:${logicalId}`,
49
+ raw: { kind: 'render', render: renderRecord },
50
+ });
51
+ };
52
+ return (_jsx(LensmcpContext.Provider, { value: ctxValue, children: _jsx(Profiler, { id: rendererId, onRender: onRender, children: props.children }) }));
53
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Single publish channel + module-level "current flow" stack. The
3
+ * `@lensmcp/client-runtime` injected into the served HTML sets
4
+ * `window.__LENSMCP_PUBLISH__` to a function that takes a partial event
5
+ * and forwards it to the LensMCP bus (via WebSocket in dev, or via the
6
+ * FrontMCP server transport in production).
7
+ *
8
+ * If the channel isn't installed we silently no-op — this keeps the
9
+ * library safe to import from any React tree (tests, SSR, etc).
10
+ */
11
+ import type { ReactPublishPayload } from './types.js';
12
+ export type PublishCategory = 'render' | 'state' | 'runtime' | 'network' | 'visual' | 'trace';
13
+ export interface FlowSpec {
14
+ flowId: string;
15
+ originType?: string;
16
+ originNodeId?: string;
17
+ causedByNodeId?: string;
18
+ }
19
+ export interface PublishEnvelope {
20
+ source: 'react' | 'valtio' | 'client-runtime' | 'visual';
21
+ category: PublishCategory;
22
+ severity?: 'debug' | 'info' | 'warning' | 'error';
23
+ title: string;
24
+ message?: string;
25
+ fingerprint?: string;
26
+ /** Optional context overrides; auto-stamped flow context is merged underneath. */
27
+ context?: Partial<{
28
+ flowId: string;
29
+ originType: string;
30
+ originNodeId: string;
31
+ causedByNodeId: string;
32
+ route: string;
33
+ url: string;
34
+ }>;
35
+ raw: ReactPublishPayload | Record<string, unknown>;
36
+ }
37
+ type PublishFn = (event: PublishEnvelope) => void;
38
+ declare global {
39
+ var __LENSMCP_PUBLISH__: PublishFn | undefined;
40
+ }
41
+ export declare function extendFlowWindow(spec?: FlowSpec): void;
42
+ /** The flow that owns work happening NOW: sync stack first, else the window. */
43
+ export declare function activeFlow(): FlowSpec | undefined;
44
+ /**
45
+ * Start a flow with no synchronous handler to wrap — page loads, timers,
46
+ * websocket pushes. Opens the causality window (so the async work that
47
+ * follows inherits the flow) and publishes the origin event that makes the
48
+ * flow exist in the reducer.
49
+ */
50
+ export declare function beginBackgroundFlow(origin: {
51
+ originType: string;
52
+ originNodeId?: string;
53
+ }): FlowSpec;
54
+ export declare function setPublisher(fn: PublishFn | undefined): void;
55
+ /**
56
+ * Push a flow onto the module-level stack for the duration of `fn`. Any
57
+ * `publish()` calls inside `fn` (including transitively triggered
58
+ * Valtio subscribers and React Profiler callbacks that fire synchronously)
59
+ * will inherit the flow context.
60
+ *
61
+ * NB: only synchronous propagation. For async chains (`fetch().then`,
62
+ * `setTimeout`, promises) the caller must re-enter the flow with
63
+ * `runInFlow` at the continuation site. The Phase 6.5 carryover adds
64
+ * automatic continuation tracking via an async-context shim.
65
+ */
66
+ export declare function runInFlow<T>(spec: FlowSpec, fn: () => T): T;
67
+ export declare function currentFlow(): FlowSpec | undefined;
68
+ /**
69
+ * Wrap a React event handler so every call runs inside a fresh flow.
70
+ * The flowId is generated per invocation; `originType` defaults to
71
+ * `'user-click'`. Pass `originNodeId` from the component identity
72
+ * (typically the `data-agent-component` value).
73
+ *
74
+ * The babel transform wraps `on*` JSX values blindly, so this must be
75
+ * total: non-function values (e.g. `onClick={maybeUndefined}`) pass
76
+ * through unchanged, and an already-flowed handler is returned as-is —
77
+ * layered components forwarding the same prop would otherwise nest a
78
+ * flow per layer and crash on the inner non-function (`handler is not
79
+ * a function`).
80
+ */
81
+ export declare function withFlow<TArgs extends unknown[], TResult>(handler: (...args: TArgs) => TResult, spec?: {
82
+ originType?: string;
83
+ originNodeId?: string;
84
+ }): (...args: TArgs) => TResult;
85
+ export declare function publish(env: PublishEnvelope): void;
86
+ /** Test hook — only useful in unit tests / smokes. */
87
+ export declare function drainQueue(): PublishEnvelope[];
88
+ export {};
89
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/lib/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,OAAO,GACP,SAAS,GACT,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,CAAC;IACzD,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpD;AAED,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAElD,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,mBAAmB,EAAE,SAAS,GAAG,SAAS,CAAC;CAChD;AAmBD,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAGtD;AAMD,gFAAgF;AAChF,wBAAgB,UAAU,IAAI,QAAQ,GAAG,SAAS,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAgBnG;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAc5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQ3D;AAED,wBAAgB,WAAW,IAAI,QAAQ,GAAG,SAAS,CAElD;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACvD,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,EACpC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAgC7B;AAQD,wBAAgB,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CA6BlD;AAED,sDAAsD;AACtD,wBAAgB,UAAU,IAAI,eAAe,EAAE,CAE9C"}