@lensmcp/valtio-instrumentation 1.23.13 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/preview.d.ts +45 -0
- package/lib/preview.js +1 -0
- package/lib/proxy.d.ts +5 -1
- package/lib/proxy.js +1 -1
- package/lib/types.d.ts +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -48,6 +48,8 @@ const cart = proxy(
|
|
|
48
48
|
);
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
`emitInitialPreview` is **off by default**: a store's initial state is usually the whole application state, so `store-created` carries it only for stores that opt in. Every preview — the initial one and the one on each `state-update` — is small by construction: at most 3 levels deep (deeper containers read `[Object]` / `[Array(n)]`), 20 entries per level (`"…": "+N more keys"` / `"… N more items"`), 256 characters per string, and 4 KiB in all. The walk reads only what it keeps, so a store holding ten thousand records costs twenty reads, not ten thousand. Credentials inside a preview are redacted when the event reaches the lens.
|
|
52
|
+
|
|
51
53
|
For cases where you want to declare a read path explicitly rather than infer it, use `useTrackedSnapshot`:
|
|
52
54
|
|
|
53
55
|
```ts
|
|
@@ -72,7 +74,7 @@ const snap = useTrackedSnapshot(cart, { storeId: 'cart', path: 'items' });
|
|
|
72
74
|
| `unstable_enableOp` | Re-exported from `valtio/vanilla`. |
|
|
73
75
|
| `unstable_getInternalStates` | Re-exported from `valtio/vanilla`. |
|
|
74
76
|
| `unstable_replaceInternalFunction` | Re-exported from `valtio/vanilla`. |
|
|
75
|
-
| `LensmcpProxyOptions` | Options for `proxy()`: `storeId?`, `source?`, `emitInitialPreview
|
|
77
|
+
| `LensmcpProxyOptions` | Options for `proxy()`: `storeId?`, `source?`, `emitInitialPreview?` (default `false`). |
|
|
76
78
|
| `UseSnapshotOptions` | Options for `useTrackedSnapshot()`: `storeId` (required), `path?`. |
|
|
77
79
|
|
|
78
80
|
> `proxy` and `useSnapshot` are the instrumented versions; everything else passes through from the real Valtio (imported via the `valtio/vanilla` subpath so this module never aliases itself).
|
package/lib/preview.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small previews of application state — the `preview` on a `state-update` and the opt-in
|
|
3
|
+
* `initialPreview` on a `store-created` (issues/closed/codex-security-43).
|
|
4
|
+
*
|
|
5
|
+
* WHY NOT `boundedClone`: that walk exists to protect a REDUCER, so it keeps every entry under
|
|
6
|
+
* 512 B "unconditionally, budget or not" — the rule that guarantees `raw.kind` and every small
|
|
7
|
+
* identifier survive a clamp. A preview has no discriminators to protect, and the most common store
|
|
8
|
+
* shape is exactly the one that rule lets through whole: a normalised entity map, thousands of small
|
|
9
|
+
* records. Measured: a 10 000-customer `users` map produced a 735 KB preview with every record in it,
|
|
10
|
+
* reported as `truncated: false`, against a 32 KiB budget. Its slow path also walks the WHOLE value
|
|
11
|
+
* once more to report `originalBytes` — on the browser's main thread, per mutated leaf.
|
|
12
|
+
*
|
|
13
|
+
* HERE every bound is hard and the walk costs what it keeps, not what the value holds:
|
|
14
|
+
* - containers deeper than `maxDepth` become a summary (`[Object]`, `[Array(120)]`) — never entered,
|
|
15
|
+
* - at most `maxEntries` entries per object/array are READ; the rest are counted, not visited,
|
|
16
|
+
* - strings (and keys) are clipped to `maxStringChars`,
|
|
17
|
+
* - the whole preview fits `maxBytes` of (approximate) JSON, overflow markers included.
|
|
18
|
+
*
|
|
19
|
+
* Counting an object's keys is the one cost proportional to the value, and it is paid only for a
|
|
20
|
+
* container the preview actually opens: a key list, no values read.
|
|
21
|
+
*
|
|
22
|
+
* Redaction is NOT done here: the bus redactor (`@lensmcp/core` `createEventRedactor`) covers every
|
|
23
|
+
* event at ingest. This walk only removes and clips, and keeps every kept key verbatim, so a
|
|
24
|
+
* credential the preview keeps is still one the bus redactor can name.
|
|
25
|
+
*/
|
|
26
|
+
export interface PreviewLimits {
|
|
27
|
+
/** Approximate serialized bytes the whole preview may take. Below {@link MIN_VALUE_BYTES} ⇒ no preview. */
|
|
28
|
+
maxBytes: number;
|
|
29
|
+
/** Container levels opened, the root included. A container below them becomes a summary. */
|
|
30
|
+
maxDepth: number;
|
|
31
|
+
/** Entries read per object or array; the rest are summarised by count. */
|
|
32
|
+
maxEntries: number;
|
|
33
|
+
/** Characters kept per string value and per key. */
|
|
34
|
+
maxStringChars: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Defaults: three levels — the depth `LENSMCP_VALTIO_PATH_DEPTH` reports read paths at — twenty
|
|
38
|
+
* entries a level, and 4 KiB in all, the same ceiling one subscription aggregate is held to.
|
|
39
|
+
*/
|
|
40
|
+
export declare const DEFAULT_PREVIEW_LIMITS: PreviewLimits;
|
|
41
|
+
/**
|
|
42
|
+
* A bounded, JSON-safe preview of `value`, or `undefined` when the budget leaves no room for one.
|
|
43
|
+
* Never throws for a value that is merely large or deep; a getter that throws propagates.
|
|
44
|
+
*/
|
|
45
|
+
export declare function previewValue(value: unknown, limits?: PreviewLimits): unknown;
|
package/lib/preview.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var A=Object.defineProperty;var n=(t,e)=>A(t,"name",{value:e,configurable:!0});var E=Object.defineProperty,l=n((t,e)=>E(t,"name",{value:e,configurable:!0}),"s");export const DEFAULT_PREVIEW_LIMITS={maxBytes:4096,maxDepth:3,maxEntries:20,maxStringChars:256};const a=32,d=32,O=2+a+d;export function previewValue(t,e=DEFAULT_PREVIEW_LIMITS){if(!(e.maxBytes>=a))return;const u={limits:e,budget:e.maxBytes};return m(t,u,1,0)}n(previewValue,"previewValue"),l(previewValue,"previewValue");function m(t,e,u,i){switch(typeof t){case"string":return f(t,Math.min(e.limits.maxStringChars,e.budget-i-2),e);case"number":return o(e,Number.isFinite(t)?t:null);case"boolean":return o(e,t);case"bigint":return f(t.toString(),a-2,e);case"undefined":case"function":case"symbol":return;default:break}if(t===null)return o(e,null);if(t instanceof Date)return Number.isNaN(t.getTime())?o(e,null):f(t.toISOString(),a-2,e);if(ArrayBuffer.isView(t))return c(e,`[${Object.prototype.toString.call(t).slice(8,-1)}(${t.byteLength} B)]`);const r=u<=e.limits.maxDepth&&e.budget-i>=O;return Array.isArray(t)?r?S(t,e,u,i):c(e,`[Array(${t.length})]`):r?v(t,e,u,i):c(e,"[Object]")}n(m,"l"),l(m,"previewOf");function S(t,e,u,i){const r=[];e.budget-=2;const g=i+d,h=Math.min(t.length,e.limits.maxEntries);for(let s=0;s<h&&!(e.budget-g-1<a);s++){e.budget-=1;const b=m(t[s],e,u+1,g);r.push(b===void 0?o(e,null):b)}return r.length<t.length&&r.push(c(e,`\u2026 ${t.length-r.length} more items`)),r}n(S,"k"),l(S,"previewArray");function v(t,e,u,i){const r={};e.budget-=2;const g=i+d,h=Object.keys(t);let s=0;for(const b of h){if(s>=e.limits.maxEntries)break;const p=$(b,e.limits.maxStringChars),y=p.length+4;if(e.budget-g-y<a)break;const w=e.budget;e.budget-=y;const x=m(t[b],e,u+1,g);x===void 0?e.budget=w:r[p]=x,s+=1}return s<h.length&&(e.budget-=5,r["\u2026"]=c(e,`+${h.length-s} more keys`)),r}n(v,"A"),l(v,"previewObject");function o(t,e){return t.budget-=e===null?4:String(e).length,e}n(o,"b"),l(o,"spend");function c(t,e){return t.budget-=e.length+2,e}n(c,"d"),l(c,"summary");function f(t,e,u){let i=t;if(t.length>e){const r=Math.max(0,Math.floor(e)-11-String(t.length).length);i=`${t.slice(0,r)}\u2026 (+${t.length-r} chars)`}return u.budget-=i.length+2,i}n(f,"a"),l(f,"clipString");function $(t,e){return t.length<=e?t:`${t.slice(0,e)}\u2026`}n($,"M"),l($,"clipKey");
|
package/lib/proxy.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ export interface LensmcpProxyOptions {
|
|
|
3
3
|
storeId?: string;
|
|
4
4
|
/** Source path (file:line) the proxy was created at. */
|
|
5
5
|
source?: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Include a bounded preview of the initial state in the `store-created` event. Off by default: the
|
|
8
|
+
* initial store is typically the whole application state, and what `valtio://stores` returned to
|
|
9
|
+
* any MCP client (issues/closed/codex-security-43). Opt in per store.
|
|
10
|
+
*/
|
|
7
11
|
emitInitialPreview?: boolean;
|
|
8
12
|
}
|
|
9
13
|
/**
|
package/lib/proxy.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var h=Object.defineProperty;var s=(e,t)=>h(e,"name",{value:t,configurable:!0});var g=Object.defineProperty,n=s((e,t)=>g(e,"name",{value:t,configurable:!0}),"s");import{proxy as P,subscribe as I,unstable_enableOp as E}from"valtio/vanilla";import{publish as w,publishLazy as x,depsHash as f}from"@lensmcp/react-instrumentation";import{DEFAULT_PREVIEW_LIMITS as l,previewValue as $}from"./preview.js";import{registerStore as S,subscribersForPathAndParents as _,tagProxy as L}from"./store-registry.js";import{truncatePath as V}from"./snapshot.js";let d=!1;function v(){if(!d)try{E(!0),d=!0}catch{}}s(v,"$"),n(v,"ensureOpsEnabled");let O=0;export function proxy(e,t={}){v();const r=t.storeId??`valtio:store:${++O}`;S(r,t.source);const a=P(e);return L(a,r),w({source:"valtio",category:"state",severity:"info",title:`store-created ${r}`,fingerprint:`valtio-store:${r}`,raw:{kind:"store-created",store:{storeId:r,source:t.source,initialPreview:t.emitInitialPreview===!0?c(e):void 0}}}),I(a,m=>{for(const b of m){const[,o,u,y]=b;if(!(o??[]).length)continue;const p=`valtio-update:${r}:${V(o??[])}`;x(p,()=>{const i=(o??[]).map(String).join(".");return{source:"valtio",category:"state",severity:"info",title:`state-update ${r}.${i}`,fingerprint:p,raw:{kind:"state-update",update:{storeId:r,path:i,changedKeys:(o??[]).map(String),beforeHash:f([y]),afterHash:f([u]),preview:c(u),subscribers:_(r,i)}}}})}},!0),a}s(proxy,"proxy"),n(proxy,"proxy");const T=(()=>{const e=typeof process<"u"?process.env?.LENSMCP_VALTIO_PREVIEW_BYTES:void 0,t=Number(e),r=e!==void 0&&e!==""&&Number.isFinite(t)&&t>=0;return{...l,maxBytes:r?Math.floor(t):l.maxBytes}})();function c(e){try{return $(e,T)}catch{return}}s(c,"d"),n(c,"safePreview");
|
package/lib/types.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface ValtioStoreRecord {
|
|
|
20
20
|
/**
|
|
21
21
|
* One commit's worth of subscription changes for one hook instance — NOT one path.
|
|
22
22
|
*
|
|
23
|
-
* issues/codex-security
|
|
23
|
+
* issues/closed/codex-security-58: this used to be one event per path, so a component reading N entries of a
|
|
24
24
|
* large collection published O(N) events per commit and another O(N) on unmount. `paths` carries the
|
|
25
25
|
* whole delta in a single event; `path` is `paths[0]`, kept so a consumer written against the old
|
|
26
26
|
* one-path-per-event shape still keys on a real path.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/valtio-instrumentation",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@lensmcp/protocol-types": "1.
|
|
18
|
-
"@lensmcp/react-instrumentation": "1.
|
|
17
|
+
"@lensmcp/protocol-types": "1.24.0",
|
|
18
|
+
"@lensmcp/react-instrumentation": "1.24.0",
|
|
19
19
|
"proxy-compare": "^3.0.0",
|
|
20
20
|
"tslib": "^2.3.0"
|
|
21
21
|
},
|