react-context-compressor 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/README.md +23 -3
- package/dist/{chunk-VBOCWPM5.mjs → chunk-X2A2RYP4.mjs} +66 -8
- package/dist/index.cjs +64 -6
- package/dist/index.mjs +1 -1
- package/dist/react/index.cjs +64 -6
- package/dist/react/index.mjs +1 -1
- package/package.json +7 -2
- package/dist/chunk-VBOCWPM5.mjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# react-context-compressor
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-context-compressor)
|
|
4
|
+
[](https://github.com/Muhammad-UmairAli/react-context-compressor/actions/workflows/ci.yml)
|
|
4
5
|
[](https://bundlephobia.com/package/react-context-compressor)
|
|
5
6
|
[](https://www.npmjs.com/package/react-context-compressor?activeTab=dependencies)
|
|
6
7
|
[](./LICENSE)
|
|
@@ -85,9 +86,10 @@ Components, on React 17 / 18 / 19.
|
|
|
85
86
|
| Sanitize | `sanitize`, `defaultSanitize`, … | Redact/remove sensitive fields (see below). |
|
|
86
87
|
|
|
87
88
|
It also handles awkward inputs predictably: **circular references** → `"[Circular]"`,
|
|
88
|
-
a **throwing getter** → `"[Getter]"` (never crashes), `Date`
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
a **throwing getter** → `"[Getter]"` (never crashes), `Date` → a copy, `Map` → plain
|
|
90
|
+
object, `Set` / `TypedArray` → array, `RegExp` → its source string, `Error` →
|
|
91
|
+
`{ name, message }`, functions/symbols dropped, `BigInt` → string. The input object
|
|
92
|
+
is **never mutated**, and output is **deterministic**.
|
|
91
93
|
|
|
92
94
|
## Sanitization (data safety)
|
|
93
95
|
|
|
@@ -164,6 +166,24 @@ network calls, or run a local model. It is a mechanical, zero-cost, client-side
|
|
|
164
166
|
object parser — that's the whole point (it saves you money _before_ the network
|
|
165
167
|
layer). See [SPLIT-PLAN §2 (out of scope)](./SPLIT-PLAN.md).
|
|
166
168
|
|
|
169
|
+
## Contributing & releasing
|
|
170
|
+
|
|
171
|
+
Development uses Git Flow: feature branches → `develop` (PR required), releases
|
|
172
|
+
promote `develop` → `main`.
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
npm install
|
|
176
|
+
npm test # vitest
|
|
177
|
+
npm run typecheck && npm run lint && npm run build && npm run size
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Each change adds a [changeset](https://github.com/changesets/changesets)
|
|
181
|
+
(`npm run changeset`). To cut a release: branch `release/X.Y.Z` off `develop`,
|
|
182
|
+
run `npx changeset version` (bumps the version + updates the changelog), open a
|
|
183
|
+
PR to `main`, merge, then tag `vX.Y.Z`. Pushing the tag triggers
|
|
184
|
+
[`.github/workflows/release.yml`](./.github/workflows/release.yml), which
|
|
185
|
+
publishes to npm **with provenance** (once an `NPM_TOKEN` secret is configured).
|
|
186
|
+
|
|
167
187
|
## License
|
|
168
188
|
|
|
169
189
|
MIT
|
|
@@ -114,6 +114,14 @@ function isEmptyValue(value) {
|
|
|
114
114
|
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
115
115
|
return false;
|
|
116
116
|
}
|
|
117
|
+
function coerceRedactedValue(value) {
|
|
118
|
+
if (value === null || value === void 0) return DEFAULTS.redactedValue;
|
|
119
|
+
try {
|
|
120
|
+
return String(value);
|
|
121
|
+
} catch {
|
|
122
|
+
return DEFAULTS.redactedValue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
117
125
|
function resolveOptions(options) {
|
|
118
126
|
return {
|
|
119
127
|
maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,
|
|
@@ -123,7 +131,7 @@ function resolveOptions(options) {
|
|
|
123
131
|
sanitize: options.sanitize ?? DEFAULTS.sanitize,
|
|
124
132
|
defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,
|
|
125
133
|
sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,
|
|
126
|
-
redactedValue: options.redactedValue
|
|
134
|
+
redactedValue: coerceRedactedValue(options.redactedValue)
|
|
127
135
|
};
|
|
128
136
|
}
|
|
129
137
|
function walkObjectInto(out, source, depth, opts, seen) {
|
|
@@ -155,27 +163,67 @@ function walk(value, depth, opts, seen) {
|
|
|
155
163
|
if (type !== "object") return value;
|
|
156
164
|
const ref = value;
|
|
157
165
|
if (seen.has(ref)) return CIRCULAR;
|
|
158
|
-
if (value instanceof Date) return value;
|
|
166
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
167
|
+
if (value instanceof RegExp) {
|
|
168
|
+
try {
|
|
169
|
+
return value.toString();
|
|
170
|
+
} catch {
|
|
171
|
+
return GETTER_ERROR;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (value instanceof Error) {
|
|
175
|
+
try {
|
|
176
|
+
return walk({ name: value.name, message: value.message }, depth, opts, seen);
|
|
177
|
+
} catch {
|
|
178
|
+
return GETTER_ERROR;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
182
|
+
try {
|
|
183
|
+
return walk(Array.from(value), depth, opts, seen);
|
|
184
|
+
} catch {
|
|
185
|
+
return GETTER_ERROR;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
159
188
|
if (value instanceof Map) {
|
|
160
189
|
seen.add(ref);
|
|
161
190
|
const obj = {};
|
|
162
|
-
|
|
191
|
+
try {
|
|
192
|
+
for (const [k, v] of value) safeAssign(obj, String(k), v);
|
|
193
|
+
} catch {
|
|
194
|
+
seen.delete(ref);
|
|
195
|
+
return GETTER_ERROR;
|
|
196
|
+
}
|
|
163
197
|
const result = walk(obj, depth, opts, seen);
|
|
164
198
|
seen.delete(ref);
|
|
165
199
|
return result;
|
|
166
200
|
}
|
|
167
201
|
if (value instanceof Set) {
|
|
168
202
|
seen.add(ref);
|
|
169
|
-
|
|
203
|
+
let items;
|
|
204
|
+
try {
|
|
205
|
+
items = Array.from(value);
|
|
206
|
+
} catch {
|
|
207
|
+
seen.delete(ref);
|
|
208
|
+
return GETTER_ERROR;
|
|
209
|
+
}
|
|
210
|
+
const result = walk(items, depth, opts, seen);
|
|
170
211
|
seen.delete(ref);
|
|
171
212
|
return result;
|
|
172
213
|
}
|
|
173
214
|
if (Array.isArray(value)) {
|
|
174
215
|
if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;
|
|
175
216
|
seen.add(ref);
|
|
176
|
-
const
|
|
217
|
+
const cap = Math.min(value.length, opts.maxArrayLength);
|
|
177
218
|
const out2 = [];
|
|
178
|
-
for (
|
|
219
|
+
for (let i = 0; i < cap; i++) {
|
|
220
|
+
let item;
|
|
221
|
+
try {
|
|
222
|
+
item = value[i];
|
|
223
|
+
} catch {
|
|
224
|
+
out2.push(GETTER_ERROR);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
179
227
|
const walked = walk(item, depth + 1, opts, seen);
|
|
180
228
|
out2.push(walked === OMIT ? null : walked);
|
|
181
229
|
}
|
|
@@ -192,8 +240,18 @@ function walk(value, depth, opts, seen) {
|
|
|
192
240
|
seen.delete(ref);
|
|
193
241
|
return out;
|
|
194
242
|
}
|
|
243
|
+
function warnIfRedactionDisabled(opts) {
|
|
244
|
+
if (opts.defaultSanitize || opts.sanitize.length > 0) return;
|
|
245
|
+
const proc = globalThis.process;
|
|
246
|
+
if (typeof proc !== "undefined" && proc.env?.NODE_ENV !== "production") {
|
|
247
|
+
console.warn(
|
|
248
|
+
"react-context-compressor: redaction is fully disabled (defaultSanitize:false and no sanitize matchers); sensitive fields will NOT be redacted."
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
195
252
|
function compressCore(state, options = {}) {
|
|
196
253
|
const opts = resolveOptions(options);
|
|
254
|
+
warnIfRedactionDisabled(opts);
|
|
197
255
|
const walked = walk(state, 0, opts, /* @__PURE__ */ new WeakSet());
|
|
198
256
|
return walked === OMIT ? void 0 : walked;
|
|
199
257
|
}
|
|
@@ -204,5 +262,5 @@ function compress(state, options = {}) {
|
|
|
204
262
|
}
|
|
205
263
|
|
|
206
264
|
export { compress };
|
|
207
|
-
//# sourceMappingURL=chunk-
|
|
208
|
-
//# sourceMappingURL=chunk-
|
|
265
|
+
//# sourceMappingURL=chunk-X2A2RYP4.mjs.map
|
|
266
|
+
//# sourceMappingURL=chunk-X2A2RYP4.mjs.map
|
package/dist/index.cjs
CHANGED
|
@@ -116,6 +116,14 @@ function isEmptyValue(value) {
|
|
|
116
116
|
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
119
|
+
function coerceRedactedValue(value) {
|
|
120
|
+
if (value === null || value === void 0) return DEFAULTS.redactedValue;
|
|
121
|
+
try {
|
|
122
|
+
return String(value);
|
|
123
|
+
} catch {
|
|
124
|
+
return DEFAULTS.redactedValue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
119
127
|
function resolveOptions(options) {
|
|
120
128
|
return {
|
|
121
129
|
maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,
|
|
@@ -125,7 +133,7 @@ function resolveOptions(options) {
|
|
|
125
133
|
sanitize: options.sanitize ?? DEFAULTS.sanitize,
|
|
126
134
|
defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,
|
|
127
135
|
sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,
|
|
128
|
-
redactedValue: options.redactedValue
|
|
136
|
+
redactedValue: coerceRedactedValue(options.redactedValue)
|
|
129
137
|
};
|
|
130
138
|
}
|
|
131
139
|
function walkObjectInto(out, source, depth, opts, seen) {
|
|
@@ -157,27 +165,67 @@ function walk(value, depth, opts, seen) {
|
|
|
157
165
|
if (type !== "object") return value;
|
|
158
166
|
const ref = value;
|
|
159
167
|
if (seen.has(ref)) return CIRCULAR;
|
|
160
|
-
if (value instanceof Date) return value;
|
|
168
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
169
|
+
if (value instanceof RegExp) {
|
|
170
|
+
try {
|
|
171
|
+
return value.toString();
|
|
172
|
+
} catch {
|
|
173
|
+
return GETTER_ERROR;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (value instanceof Error) {
|
|
177
|
+
try {
|
|
178
|
+
return walk({ name: value.name, message: value.message }, depth, opts, seen);
|
|
179
|
+
} catch {
|
|
180
|
+
return GETTER_ERROR;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
184
|
+
try {
|
|
185
|
+
return walk(Array.from(value), depth, opts, seen);
|
|
186
|
+
} catch {
|
|
187
|
+
return GETTER_ERROR;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
161
190
|
if (value instanceof Map) {
|
|
162
191
|
seen.add(ref);
|
|
163
192
|
const obj = {};
|
|
164
|
-
|
|
193
|
+
try {
|
|
194
|
+
for (const [k, v] of value) safeAssign(obj, String(k), v);
|
|
195
|
+
} catch {
|
|
196
|
+
seen.delete(ref);
|
|
197
|
+
return GETTER_ERROR;
|
|
198
|
+
}
|
|
165
199
|
const result = walk(obj, depth, opts, seen);
|
|
166
200
|
seen.delete(ref);
|
|
167
201
|
return result;
|
|
168
202
|
}
|
|
169
203
|
if (value instanceof Set) {
|
|
170
204
|
seen.add(ref);
|
|
171
|
-
|
|
205
|
+
let items;
|
|
206
|
+
try {
|
|
207
|
+
items = Array.from(value);
|
|
208
|
+
} catch {
|
|
209
|
+
seen.delete(ref);
|
|
210
|
+
return GETTER_ERROR;
|
|
211
|
+
}
|
|
212
|
+
const result = walk(items, depth, opts, seen);
|
|
172
213
|
seen.delete(ref);
|
|
173
214
|
return result;
|
|
174
215
|
}
|
|
175
216
|
if (Array.isArray(value)) {
|
|
176
217
|
if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;
|
|
177
218
|
seen.add(ref);
|
|
178
|
-
const
|
|
219
|
+
const cap = Math.min(value.length, opts.maxArrayLength);
|
|
179
220
|
const out2 = [];
|
|
180
|
-
for (
|
|
221
|
+
for (let i = 0; i < cap; i++) {
|
|
222
|
+
let item;
|
|
223
|
+
try {
|
|
224
|
+
item = value[i];
|
|
225
|
+
} catch {
|
|
226
|
+
out2.push(GETTER_ERROR);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
181
229
|
const walked = walk(item, depth + 1, opts, seen);
|
|
182
230
|
out2.push(walked === OMIT ? null : walked);
|
|
183
231
|
}
|
|
@@ -194,8 +242,18 @@ function walk(value, depth, opts, seen) {
|
|
|
194
242
|
seen.delete(ref);
|
|
195
243
|
return out;
|
|
196
244
|
}
|
|
245
|
+
function warnIfRedactionDisabled(opts) {
|
|
246
|
+
if (opts.defaultSanitize || opts.sanitize.length > 0) return;
|
|
247
|
+
const proc = globalThis.process;
|
|
248
|
+
if (typeof proc !== "undefined" && proc.env?.NODE_ENV !== "production") {
|
|
249
|
+
console.warn(
|
|
250
|
+
"react-context-compressor: redaction is fully disabled (defaultSanitize:false and no sanitize matchers); sensitive fields will NOT be redacted."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
197
254
|
function compressCore(state, options = {}) {
|
|
198
255
|
const opts = resolveOptions(options);
|
|
256
|
+
warnIfRedactionDisabled(opts);
|
|
199
257
|
const walked = walk(state, 0, opts, /* @__PURE__ */ new WeakSet());
|
|
200
258
|
return walked === OMIT ? void 0 : walked;
|
|
201
259
|
}
|
package/dist/index.mjs
CHANGED
package/dist/react/index.cjs
CHANGED
|
@@ -120,6 +120,14 @@ function isEmptyValue(value) {
|
|
|
120
120
|
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
121
121
|
return false;
|
|
122
122
|
}
|
|
123
|
+
function coerceRedactedValue(value) {
|
|
124
|
+
if (value === null || value === void 0) return DEFAULTS.redactedValue;
|
|
125
|
+
try {
|
|
126
|
+
return String(value);
|
|
127
|
+
} catch {
|
|
128
|
+
return DEFAULTS.redactedValue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
123
131
|
function resolveOptions(options) {
|
|
124
132
|
return {
|
|
125
133
|
maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,
|
|
@@ -129,7 +137,7 @@ function resolveOptions(options) {
|
|
|
129
137
|
sanitize: options.sanitize ?? DEFAULTS.sanitize,
|
|
130
138
|
defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,
|
|
131
139
|
sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,
|
|
132
|
-
redactedValue: options.redactedValue
|
|
140
|
+
redactedValue: coerceRedactedValue(options.redactedValue)
|
|
133
141
|
};
|
|
134
142
|
}
|
|
135
143
|
function walkObjectInto(out, source, depth, opts, seen) {
|
|
@@ -161,27 +169,67 @@ function walk(value, depth, opts, seen) {
|
|
|
161
169
|
if (type !== "object") return value;
|
|
162
170
|
const ref = value;
|
|
163
171
|
if (seen.has(ref)) return CIRCULAR;
|
|
164
|
-
if (value instanceof Date) return value;
|
|
172
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
173
|
+
if (value instanceof RegExp) {
|
|
174
|
+
try {
|
|
175
|
+
return value.toString();
|
|
176
|
+
} catch {
|
|
177
|
+
return GETTER_ERROR;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (value instanceof Error) {
|
|
181
|
+
try {
|
|
182
|
+
return walk({ name: value.name, message: value.message }, depth, opts, seen);
|
|
183
|
+
} catch {
|
|
184
|
+
return GETTER_ERROR;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
188
|
+
try {
|
|
189
|
+
return walk(Array.from(value), depth, opts, seen);
|
|
190
|
+
} catch {
|
|
191
|
+
return GETTER_ERROR;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
165
194
|
if (value instanceof Map) {
|
|
166
195
|
seen.add(ref);
|
|
167
196
|
const obj = {};
|
|
168
|
-
|
|
197
|
+
try {
|
|
198
|
+
for (const [k, v] of value) safeAssign(obj, String(k), v);
|
|
199
|
+
} catch {
|
|
200
|
+
seen.delete(ref);
|
|
201
|
+
return GETTER_ERROR;
|
|
202
|
+
}
|
|
169
203
|
const result = walk(obj, depth, opts, seen);
|
|
170
204
|
seen.delete(ref);
|
|
171
205
|
return result;
|
|
172
206
|
}
|
|
173
207
|
if (value instanceof Set) {
|
|
174
208
|
seen.add(ref);
|
|
175
|
-
|
|
209
|
+
let items;
|
|
210
|
+
try {
|
|
211
|
+
items = Array.from(value);
|
|
212
|
+
} catch {
|
|
213
|
+
seen.delete(ref);
|
|
214
|
+
return GETTER_ERROR;
|
|
215
|
+
}
|
|
216
|
+
const result = walk(items, depth, opts, seen);
|
|
176
217
|
seen.delete(ref);
|
|
177
218
|
return result;
|
|
178
219
|
}
|
|
179
220
|
if (Array.isArray(value)) {
|
|
180
221
|
if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;
|
|
181
222
|
seen.add(ref);
|
|
182
|
-
const
|
|
223
|
+
const cap = Math.min(value.length, opts.maxArrayLength);
|
|
183
224
|
const out2 = [];
|
|
184
|
-
for (
|
|
225
|
+
for (let i = 0; i < cap; i++) {
|
|
226
|
+
let item;
|
|
227
|
+
try {
|
|
228
|
+
item = value[i];
|
|
229
|
+
} catch {
|
|
230
|
+
out2.push(GETTER_ERROR);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
185
233
|
const walked = walk(item, depth + 1, opts, seen);
|
|
186
234
|
out2.push(walked === OMIT ? null : walked);
|
|
187
235
|
}
|
|
@@ -198,8 +246,18 @@ function walk(value, depth, opts, seen) {
|
|
|
198
246
|
seen.delete(ref);
|
|
199
247
|
return out;
|
|
200
248
|
}
|
|
249
|
+
function warnIfRedactionDisabled(opts) {
|
|
250
|
+
if (opts.defaultSanitize || opts.sanitize.length > 0) return;
|
|
251
|
+
const proc = globalThis.process;
|
|
252
|
+
if (typeof proc !== "undefined" && proc.env?.NODE_ENV !== "production") {
|
|
253
|
+
console.warn(
|
|
254
|
+
"react-context-compressor: redaction is fully disabled (defaultSanitize:false and no sanitize matchers); sensitive fields will NOT be redacted."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
201
258
|
function compressCore(state, options = {}) {
|
|
202
259
|
const opts = resolveOptions(options);
|
|
260
|
+
warnIfRedactionDisabled(opts);
|
|
203
261
|
const walked = walk(state, 0, opts, /* @__PURE__ */ new WeakSet());
|
|
204
262
|
return walked === OMIT ? void 0 : walked;
|
|
205
263
|
}
|
package/dist/react/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-context-compressor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Mechanical, zero-dependency client-side compressor + sanitizer that turns React/JS app state into a minimal, safe payload for LLMs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"files": [
|
|
32
|
-
"dist"
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/**/*.map"
|
|
33
34
|
],
|
|
34
35
|
"main": "./dist/index.cjs",
|
|
35
36
|
"module": "./dist/index.mjs",
|
|
@@ -67,6 +68,8 @@
|
|
|
67
68
|
"test:watch": "vitest",
|
|
68
69
|
"test:cov": "vitest run --coverage",
|
|
69
70
|
"size": "size-limit",
|
|
71
|
+
"attw": "attw --pack",
|
|
72
|
+
"publint": "publint",
|
|
70
73
|
"changeset": "changeset",
|
|
71
74
|
"prepublishOnly": "npm run build"
|
|
72
75
|
},
|
|
@@ -85,6 +88,7 @@
|
|
|
85
88
|
"node": ">=18"
|
|
86
89
|
},
|
|
87
90
|
"devDependencies": {
|
|
91
|
+
"@arethetypeswrong/cli": "^0.18.3",
|
|
88
92
|
"@biomejs/biome": "^2.5.0",
|
|
89
93
|
"@changesets/cli": "^2.31.0",
|
|
90
94
|
"@size-limit/preset-small-lib": "^12.1.0",
|
|
@@ -93,6 +97,7 @@
|
|
|
93
97
|
"@types/react-dom": "^19.2.3",
|
|
94
98
|
"@vitest/coverage-v8": "^4.1.9",
|
|
95
99
|
"jsdom": "^29.1.1",
|
|
100
|
+
"publint": "^0.3.21",
|
|
96
101
|
"react": "^19.2.7",
|
|
97
102
|
"react-dom": "^19.2.7",
|
|
98
103
|
"size-limit": "^12.1.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/sanitize.ts","../src/core/compress.ts","../src/index.ts"],"names":["out"],"mappings":"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","file":"chunk-VBOCWPM5.mjs","sourcesContent":["/**\n * Client-side data-safety layer — detect sensitive field names so the walker\n * can redact or remove them BEFORE their values are ever read. Mechanical and\n * key-name-driven: no value inspection, no network, no models.\n *\n * Limitations (key-name-driven by design):\n * - Matches field NAMES, not values: a secret under an innocuous key, or as a\n * bare array/Set element (no key), is NOT detected.\n * - Only own enumerable string keys are processed; Symbol-keyed / non-enumerable\n * properties are dropped by the walker, never scanned.\n * - Homoglyph attacks (e.g. Cyrillic look-alikes) are not fully closed; keys are\n * NFKC-normalized and zero-width-stripped before matching, which defeats the\n * common fullwidth/zero-width evasions but not deliberate confusables.\n */\n\n/** Default replacement value used when a sensitive field is redacted. */\nexport const REDACTED = \"[REDACTED]\";\n\n/**\n * Built-in deny-list of sensitive field-name patterns (case-insensitive).\n * Patterns are anchored to avoid false positives on common keys — e.g. it does\n * NOT redact `author`, `dashboard`, `secretary`, `tokenCount`, `promptTokens`,\n * or `accessKeyboard`.\n */\nexport const DEFAULT_DENY_LIST: readonly RegExp[] = [\n // Passwords / pass-phrases\n /pass(?:word|wd|phrase)/i,\n // Secrets (\"secret\", \"clientSecret\", \"secretKey\") but not \"secretary\"\n /secret(?!ary)/i,\n // Tokens — standalone or with an auth-ish prefix; NOT tokenCount/tokenize/promptTokens\n /\\btoken\\b/i,\n /(?:access|refresh|id|auth|bearer|api|csrf|xsrf|session|sso|oauth|reset|verification|activation)[-_]?token/i,\n // Keys (\"apiKey\", \"accessKey\", \"signingKey\") but not \"accessKeyword\"/\"keyboard\"\n /(?:api|access|private|signing|encryption)[-_]?keys?(?![a-z])/i,\n /\\bjwt\\b/i,\n /authorization/i,\n /bearer/i,\n /credentials?/i,\n /cookie/i,\n /session[-_]?(?:id|token|key)/i,\n // 2FA / recovery\n /\\b(?:otp|totp|mfa|2fa)\\b/i,\n /(?:recovery|backup)[-_]?codes?/i,\n /\\bmnemonic\\b/i,\n /seed[-_]?phrase/i,\n // Crypto / signing\n /\\bhmac\\b/i,\n /\\bsignature\\b/i,\n /\\b[cx]srf\\b/i,\n // Connection strings / DB credentials\n /connection[-_]?string/i,\n /\\bdsn\\b/i,\n /(?:database|db)[-_]?(?:url|uri)/i,\n // PII / financial\n /ssn/i,\n /social[-_]?security[-_]?(?:number|no)?/i,\n /credit[-_]?card/i,\n /card[-_]?number/i,\n /cvv2?/i,\n /\\bpin\\b/i,\n /\\biban\\b/i,\n /routing[-_]?number/i,\n /account[-_]?number/i,\n /\\bpassport\\b/i,\n /tax[-_]?id/i,\n];\n\n/** Zero-width / default-ignorable code points used to evade name matching. */\nconst ZERO_WIDTH = /[\\u00AD\\u200B-\\u200D\\u2060\\uFEFF]/g;\n\n/**\n * Normalize a key before matching: NFKC folds fullwidth/compatibility forms to\n * their ASCII equivalents, and zero-width characters are stripped. This defeats\n * the common `\"password\"` / zero-width-injected evasions.\n */\nexport function normalizeKey(key: string): string {\n return key.normalize(\"NFKC\").replace(ZERO_WIDTH, \"\");\n}\n\n/**\n * Stateless RegExp test. A user-supplied pattern with the `g`/`y` flag carries\n * a mutable `lastIndex`; resetting it keeps matching deterministic across keys.\n */\nexport function regexTest(re: RegExp, value: string): boolean {\n if (re.global || re.sticky) re.lastIndex = 0;\n return re.test(value);\n}\n\n/**\n * Is `key` a sensitive field name? Checks user matchers (string = exact,\n * case-insensitive; RegExp = pattern) and, when `useDefaults`, the built-in\n * deny-list. Keys are NFKC-normalized and zero-width-stripped first.\n */\nexport function isSensitiveKey(\n key: string,\n userMatchers: ReadonlyArray<string | RegExp>,\n useDefaults: boolean,\n): boolean {\n const normalized = normalizeKey(key);\n const lower = normalized.toLowerCase();\n for (const matcher of userMatchers) {\n if (typeof matcher === \"string\") {\n if (normalizeKey(matcher).toLowerCase() === lower) return true;\n } else if (regexTest(matcher, normalized)) {\n return true;\n }\n }\n if (useDefaults) {\n for (const re of DEFAULT_DENY_LIST) {\n if (regexTest(re, normalized)) return true;\n }\n }\n return false;\n}\n","/**\n * Core compression walker — mechanical, deterministic, zero-dependency.\n *\n * A single recursive pass applies: key stripping, depth capping, array-length\n * capping, and empty-value dropping, with circular-reference protection.\n * Sanitization (task 003) composes into this same walk via {@link CompressOptions.sanitize}.\n */\n\nimport type { CompressOptions } from \"../index\";\nimport { isSensitiveKey, REDACTED, regexTest } from \"./sanitize\";\n\n/** Marker substituted for a node that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_OBJECT = \"[Object]\";\n/** Marker substituted for an array that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_ARRAY = \"[Array]\";\n/** Marker substituted for a circular back-reference. */\nexport const CIRCULAR = \"[Circular]\";\n/** Marker substituted for a property whose getter threw when read. */\nexport const GETTER_ERROR = \"[Getter]\";\n\n/**\n * Safe default depth cap. Real application state is rarely deeper than a few\n * dozen levels; capping by default keeps payloads minimal and prevents a\n * stack-overflow DoS on pathologically deep (untrusted) input. Opt out with\n * `maxDepth: Infinity`.\n */\nexport const DEFAULT_MAX_DEPTH = 100;\n\n/** Fully-resolved options after defaults are applied. */\ninterface ResolvedOptions {\n maxDepth: number;\n maxArrayLength: number;\n strip: Array<string | RegExp>;\n dropEmpty: boolean;\n sanitize: Array<string | RegExp>;\n defaultSanitize: boolean;\n sanitizeMode: \"redact\" | \"remove\";\n redactedValue: string;\n}\n\nconst DEFAULTS: ResolvedOptions = {\n maxDepth: DEFAULT_MAX_DEPTH,\n maxArrayLength: Number.POSITIVE_INFINITY,\n strip: [],\n dropEmpty: false,\n sanitize: [],\n defaultSanitize: true,\n sanitizeMode: \"redact\",\n redactedValue: REDACTED,\n};\n\n/** A unique sentinel meaning \"this value should be omitted from the output\". */\nconst OMIT = Symbol(\"omit\");\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Assign an own, enumerable data property — even for dangerous keys like\n * `__proto__` (a plain `out[key] = v` would reassign the prototype instead of\n * creating a property, corrupting the output and silently dropping the value).\n */\nfunction safeAssign(target: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(target, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n}\n\n/** Does `key` match any matcher (exact string or RegExp test)? */\nexport function keyMatches(key: string, matchers: ReadonlyArray<string | RegExp>): boolean {\n for (const matcher of matchers) {\n if (typeof matcher === \"string\") {\n if (matcher === key) return true;\n } else if (regexTest(matcher, key)) {\n return true;\n }\n }\n return false;\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined || value === \"\") return true;\n if (Array.isArray(value)) return value.length === 0;\n if (isPlainObject(value)) return Object.keys(value).length === 0;\n return false;\n}\n\n/**\n * Resolve user options against defaults. Exposed so the React layer and\n * sanitization can share one normalization path.\n */\nexport function resolveOptions(options: CompressOptions): ResolvedOptions {\n return {\n maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,\n maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,\n strip: options.strip ?? DEFAULTS.strip,\n dropEmpty: options.dropEmpty ?? DEFAULTS.dropEmpty,\n sanitize: options.sanitize ?? DEFAULTS.sanitize,\n defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,\n sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,\n redactedValue: options.redactedValue ?? DEFAULTS.redactedValue,\n };\n}\n\n/** Walk own enumerable string keys of an object-like value into `out`. */\nfunction walkObjectInto(\n out: Record<string, unknown>,\n source: Record<string, unknown>,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): void {\n for (const key of Object.keys(source)) {\n if (keyMatches(key, opts.strip)) continue;\n // Sanitize BEFORE reading the value: a sensitive value is never read\n // (no getter fired), never walked, and never reaches the output.\n if (isSensitiveKey(key, opts.sanitize, opts.defaultSanitize)) {\n if (opts.sanitizeMode === \"remove\") continue;\n safeAssign(out, key, opts.redactedValue);\n continue;\n }\n let raw: unknown;\n try {\n raw = source[key];\n } catch {\n // A getter threw — degrade to a marker rather than crashing the walk.\n safeAssign(out, key, GETTER_ERROR);\n continue;\n }\n const walked = walk(raw, depth + 1, opts, seen);\n if (walked === OMIT) continue;\n if (opts.dropEmpty && isEmptyValue(walked)) continue;\n safeAssign(out, key, walked);\n }\n}\n\nfunction walk(\n value: unknown,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): unknown {\n if (value === null) return null;\n const type = typeof value;\n if (type === \"function\" || type === \"symbol\") return OMIT;\n // BigInt is not JSON-serializable; coerce to string for an LLM-ready payload.\n if (type === \"bigint\") return (value as bigint).toString();\n if (type !== \"object\") return value;\n\n // Circular-reference guard sits above all object normalization so a cycle\n // through a Map/Set is caught instead of overflowing the stack.\n const ref = value as object;\n if (seen.has(ref)) return CIRCULAR;\n\n // Non-plain objects we deliberately normalize for a predictable payload.\n if (value instanceof Date) return value;\n if (value instanceof Map) {\n seen.add(ref);\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value) safeAssign(obj, String(k), v);\n const result = walk(obj, depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n if (value instanceof Set) {\n seen.add(ref);\n const result = walk(Array.from(value), depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n\n if (Array.isArray(value)) {\n if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;\n seen.add(ref);\n const limited =\n value.length > opts.maxArrayLength ? value.slice(0, opts.maxArrayLength) : value;\n const out: unknown[] = [];\n for (const item of limited) {\n const walked = walk(item, depth + 1, opts, seen);\n // In arrays, an omitted item collapses to null to preserve index intent.\n out.push(walked === OMIT ? null : walked);\n }\n if (value.length > opts.maxArrayLength) {\n out.push(`[+${value.length - opts.maxArrayLength} more]`);\n }\n seen.delete(ref);\n return out;\n }\n\n // Plain objects and unknown object kinds (class instances) both rebuild as a\n // plain object from their own enumerable string keys.\n if (depth >= opts.maxDepth) return TRUNCATED_OBJECT;\n seen.add(ref);\n const out: Record<string, unknown> = {};\n walkObjectInto(out, value as Record<string, unknown>, depth, opts, seen);\n seen.delete(ref);\n return out;\n}\n\n/**\n * Mechanically compress a state value into a minimal payload, applying the\n * structural transforms in {@link CompressOptions}. Pure and deterministic;\n * the input is never mutated.\n */\nexport function compressCore(state: unknown, options: CompressOptions = {}): unknown {\n const opts = resolveOptions(options);\n const walked = walk(state, 0, opts, new WeakSet());\n // A top-level function/symbol compresses to undefined rather than a sentinel.\n return walked === OMIT ? undefined : walked;\n}\n","/**\n * react-context-compressor — core entry (framework-agnostic, zero dependencies).\n *\n * Mechanical compression lands here (task 002); sanitization composes into the\n * same walk in task 003.\n */\n\nimport { compressCore } from \"./core/compress\";\n\n/**\n * Options controlling how a state value is mechanically compressed and\n * sanitized into a minimal, LLM-ready payload. All fields are optional;\n * `compress(state)` with no options returns a safe deep copy.\n */\nexport interface CompressOptions {\n /**\n * Maximum object/array depth to retain. Nodes deeper than this are replaced\n * with a `\"[Object]\"` / `\"[Array]\"` marker. Default: `100` (a safe cap that\n * keeps payloads minimal and prevents stack overflow on pathologically deep\n * input). Set to `Infinity` to disable depth capping.\n */\n maxDepth?: number;\n /**\n * Maximum array length to retain. Longer arrays are truncated and a\n * `\"[+N more]\"` marker is appended. Default: unlimited.\n */\n maxArrayLength?: number;\n /** Keys (exact strings or patterns) to strip from the output. */\n strip?: Array<string | RegExp>;\n /** When true, drop `null` / `undefined` / `\"\"` / `[]` / `{}` values. */\n dropEmpty?: boolean;\n /**\n * Extra sensitive field-name matchers to redact/remove, IN ADDITION to the\n * built-in deny-list (unless {@link CompressOptions.defaultSanitize} is\n * `false`). A `string` matches a key case-insensitively and exactly; a\n * `RegExp` matches by pattern. Matching is by field NAME, not value.\n */\n sanitize?: Array<string | RegExp>;\n /**\n * Apply the built-in sensitive-field deny-list (password, token, secret,\n * apiKey, authorization, cookie, ssn, creditCard, …). Default: `true`.\n * Set `false` to rely solely on {@link CompressOptions.sanitize}.\n */\n defaultSanitize?: boolean;\n /**\n * How to treat a sensitive field: `\"redact\"` replaces its value with\n * {@link CompressOptions.redactedValue}; `\"remove\"` drops the key entirely.\n * Either way the value is never read or emitted. Default: `\"redact\"`.\n */\n sanitizeMode?: \"redact\" | \"remove\";\n /** Replacement used when `sanitizeMode` is `\"redact\"`. Default: `\"[REDACTED]\"`. */\n redactedValue?: string;\n}\n\n/**\n * Mechanically compress and sanitize a state value into a minimal, safe payload.\n *\n * Pure and deterministic: the same input and options always produce the same\n * output, and the input is never mutated. Performs no I/O — purely structural.\n */\nexport function compress<T>(state: T, options: CompressOptions = {}): unknown {\n return compressCore(state, options);\n}\n"]}
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/sanitize.ts","../src/core/compress.ts","../src/index.ts"],"names":["out"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Client-side data-safety layer — detect sensitive field names so the walker\n * can redact or remove them BEFORE their values are ever read. Mechanical and\n * key-name-driven: no value inspection, no network, no models.\n *\n * Limitations (key-name-driven by design):\n * - Matches field NAMES, not values: a secret under an innocuous key, or as a\n * bare array/Set element (no key), is NOT detected.\n * - Only own enumerable string keys are processed; Symbol-keyed / non-enumerable\n * properties are dropped by the walker, never scanned.\n * - Homoglyph attacks (e.g. Cyrillic look-alikes) are not fully closed; keys are\n * NFKC-normalized and zero-width-stripped before matching, which defeats the\n * common fullwidth/zero-width evasions but not deliberate confusables.\n */\n\n/** Default replacement value used when a sensitive field is redacted. */\nexport const REDACTED = \"[REDACTED]\";\n\n/**\n * Built-in deny-list of sensitive field-name patterns (case-insensitive).\n * Patterns are anchored to avoid false positives on common keys — e.g. it does\n * NOT redact `author`, `dashboard`, `secretary`, `tokenCount`, `promptTokens`,\n * or `accessKeyboard`.\n */\nexport const DEFAULT_DENY_LIST: readonly RegExp[] = [\n // Passwords / pass-phrases\n /pass(?:word|wd|phrase)/i,\n // Secrets (\"secret\", \"clientSecret\", \"secretKey\") but not \"secretary\"\n /secret(?!ary)/i,\n // Tokens — standalone or with an auth-ish prefix; NOT tokenCount/tokenize/promptTokens\n /\\btoken\\b/i,\n /(?:access|refresh|id|auth|bearer|api|csrf|xsrf|session|sso|oauth|reset|verification|activation)[-_]?token/i,\n // Keys (\"apiKey\", \"accessKey\", \"signingKey\") but not \"accessKeyword\"/\"keyboard\"\n /(?:api|access|private|signing|encryption)[-_]?keys?(?![a-z])/i,\n /\\bjwt\\b/i,\n /authorization/i,\n /bearer/i,\n /credentials?/i,\n /cookie/i,\n /session[-_]?(?:id|token|key)/i,\n // 2FA / recovery\n /\\b(?:otp|totp|mfa|2fa)\\b/i,\n /(?:recovery|backup)[-_]?codes?/i,\n /\\bmnemonic\\b/i,\n /seed[-_]?phrase/i,\n // Crypto / signing\n /\\bhmac\\b/i,\n /\\bsignature\\b/i,\n /\\b[cx]srf\\b/i,\n // Connection strings / DB credentials\n /connection[-_]?string/i,\n /\\bdsn\\b/i,\n /(?:database|db)[-_]?(?:url|uri)/i,\n // PII / financial\n /ssn/i,\n /social[-_]?security[-_]?(?:number|no)?/i,\n /credit[-_]?card/i,\n /card[-_]?number/i,\n /cvv2?/i,\n /\\bpin\\b/i,\n /\\biban\\b/i,\n /routing[-_]?number/i,\n /account[-_]?number/i,\n /\\bpassport\\b/i,\n /tax[-_]?id/i,\n];\n\n/** Zero-width / default-ignorable code points used to evade name matching. */\nconst ZERO_WIDTH = /[\\u00AD\\u200B-\\u200D\\u2060\\uFEFF]/g;\n\n/**\n * Normalize a key before matching: NFKC folds fullwidth/compatibility forms to\n * their ASCII equivalents, and zero-width characters are stripped. This defeats\n * the common `\"password\"` / zero-width-injected evasions.\n */\nexport function normalizeKey(key: string): string {\n return key.normalize(\"NFKC\").replace(ZERO_WIDTH, \"\");\n}\n\n/**\n * Stateless RegExp test. A user-supplied pattern with the `g`/`y` flag carries\n * a mutable `lastIndex`; resetting it keeps matching deterministic across keys.\n */\nexport function regexTest(re: RegExp, value: string): boolean {\n if (re.global || re.sticky) re.lastIndex = 0;\n return re.test(value);\n}\n\n/**\n * Is `key` a sensitive field name? Checks user matchers (string = exact,\n * case-insensitive; RegExp = pattern) and, when `useDefaults`, the built-in\n * deny-list. Keys are NFKC-normalized and zero-width-stripped first.\n */\nexport function isSensitiveKey(\n key: string,\n userMatchers: ReadonlyArray<string | RegExp>,\n useDefaults: boolean,\n): boolean {\n const normalized = normalizeKey(key);\n const lower = normalized.toLowerCase();\n for (const matcher of userMatchers) {\n if (typeof matcher === \"string\") {\n if (normalizeKey(matcher).toLowerCase() === lower) return true;\n } else if (regexTest(matcher, normalized)) {\n return true;\n }\n }\n if (useDefaults) {\n for (const re of DEFAULT_DENY_LIST) {\n if (regexTest(re, normalized)) return true;\n }\n }\n return false;\n}\n","/**\n * Core compression walker — mechanical, deterministic, zero-dependency.\n *\n * A single recursive pass applies: key stripping, depth capping, array-length\n * capping, and empty-value dropping, with circular-reference protection.\n * Sanitization (task 003) composes into this same walk via {@link CompressOptions.sanitize}.\n */\n\nimport type { CompressOptions } from \"../index\";\nimport { isSensitiveKey, REDACTED, regexTest } from \"./sanitize\";\n\n/** Marker substituted for a node that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_OBJECT = \"[Object]\";\n/** Marker substituted for an array that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_ARRAY = \"[Array]\";\n/** Marker substituted for a circular back-reference. */\nexport const CIRCULAR = \"[Circular]\";\n/** Marker substituted for a property whose getter threw when read. */\nexport const GETTER_ERROR = \"[Getter]\";\n\n/**\n * Safe default depth cap. Real application state is rarely deeper than a few\n * dozen levels; capping by default keeps payloads minimal and prevents a\n * stack-overflow DoS on pathologically deep (untrusted) input. Opt out with\n * `maxDepth: Infinity`.\n */\nexport const DEFAULT_MAX_DEPTH = 100;\n\n/** Fully-resolved options after defaults are applied. */\ninterface ResolvedOptions {\n maxDepth: number;\n maxArrayLength: number;\n strip: Array<string | RegExp>;\n dropEmpty: boolean;\n sanitize: Array<string | RegExp>;\n defaultSanitize: boolean;\n sanitizeMode: \"redact\" | \"remove\";\n redactedValue: string;\n}\n\nconst DEFAULTS: ResolvedOptions = {\n maxDepth: DEFAULT_MAX_DEPTH,\n maxArrayLength: Number.POSITIVE_INFINITY,\n strip: [],\n dropEmpty: false,\n sanitize: [],\n defaultSanitize: true,\n sanitizeMode: \"redact\",\n redactedValue: REDACTED,\n};\n\n/** A unique sentinel meaning \"this value should be omitted from the output\". */\nconst OMIT = Symbol(\"omit\");\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Assign an own, enumerable data property — even for dangerous keys like\n * `__proto__` (a plain `out[key] = v` would reassign the prototype instead of\n * creating a property, corrupting the output and silently dropping the value).\n */\nfunction safeAssign(target: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(target, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n}\n\n/** Does `key` match any matcher (exact string or RegExp test)? */\nexport function keyMatches(key: string, matchers: ReadonlyArray<string | RegExp>): boolean {\n for (const matcher of matchers) {\n if (typeof matcher === \"string\") {\n if (matcher === key) return true;\n } else if (regexTest(matcher, key)) {\n return true;\n }\n }\n return false;\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined || value === \"\") return true;\n if (Array.isArray(value)) return value.length === 0;\n if (isPlainObject(value)) return Object.keys(value).length === 0;\n return false;\n}\n\n/**\n * Resolve user options against defaults. Exposed so the React layer and\n * sanitization can share one normalization path.\n */\nexport function resolveOptions(options: CompressOptions): ResolvedOptions {\n return {\n maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,\n maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,\n strip: options.strip ?? DEFAULTS.strip,\n dropEmpty: options.dropEmpty ?? DEFAULTS.dropEmpty,\n sanitize: options.sanitize ?? DEFAULTS.sanitize,\n defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,\n sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,\n redactedValue: options.redactedValue ?? DEFAULTS.redactedValue,\n };\n}\n\n/** Walk own enumerable string keys of an object-like value into `out`. */\nfunction walkObjectInto(\n out: Record<string, unknown>,\n source: Record<string, unknown>,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): void {\n for (const key of Object.keys(source)) {\n if (keyMatches(key, opts.strip)) continue;\n // Sanitize BEFORE reading the value: a sensitive value is never read\n // (no getter fired), never walked, and never reaches the output.\n if (isSensitiveKey(key, opts.sanitize, opts.defaultSanitize)) {\n if (opts.sanitizeMode === \"remove\") continue;\n safeAssign(out, key, opts.redactedValue);\n continue;\n }\n let raw: unknown;\n try {\n raw = source[key];\n } catch {\n // A getter threw — degrade to a marker rather than crashing the walk.\n safeAssign(out, key, GETTER_ERROR);\n continue;\n }\n const walked = walk(raw, depth + 1, opts, seen);\n if (walked === OMIT) continue;\n if (opts.dropEmpty && isEmptyValue(walked)) continue;\n safeAssign(out, key, walked);\n }\n}\n\nfunction walk(\n value: unknown,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): unknown {\n if (value === null) return null;\n const type = typeof value;\n if (type === \"function\" || type === \"symbol\") return OMIT;\n // BigInt is not JSON-serializable; coerce to string for an LLM-ready payload.\n if (type === \"bigint\") return (value as bigint).toString();\n if (type !== \"object\") return value;\n\n // Circular-reference guard sits above all object normalization so a cycle\n // through a Map/Set is caught instead of overflowing the stack.\n const ref = value as object;\n if (seen.has(ref)) return CIRCULAR;\n\n // Non-plain objects we deliberately normalize for a predictable payload.\n if (value instanceof Date) return value;\n if (value instanceof Map) {\n seen.add(ref);\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value) safeAssign(obj, String(k), v);\n const result = walk(obj, depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n if (value instanceof Set) {\n seen.add(ref);\n const result = walk(Array.from(value), depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n\n if (Array.isArray(value)) {\n if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;\n seen.add(ref);\n const limited =\n value.length > opts.maxArrayLength ? value.slice(0, opts.maxArrayLength) : value;\n const out: unknown[] = [];\n for (const item of limited) {\n const walked = walk(item, depth + 1, opts, seen);\n // In arrays, an omitted item collapses to null to preserve index intent.\n out.push(walked === OMIT ? null : walked);\n }\n if (value.length > opts.maxArrayLength) {\n out.push(`[+${value.length - opts.maxArrayLength} more]`);\n }\n seen.delete(ref);\n return out;\n }\n\n // Plain objects and unknown object kinds (class instances) both rebuild as a\n // plain object from their own enumerable string keys.\n if (depth >= opts.maxDepth) return TRUNCATED_OBJECT;\n seen.add(ref);\n const out: Record<string, unknown> = {};\n walkObjectInto(out, value as Record<string, unknown>, depth, opts, seen);\n seen.delete(ref);\n return out;\n}\n\n/**\n * Mechanically compress a state value into a minimal payload, applying the\n * structural transforms in {@link CompressOptions}. Pure and deterministic;\n * the input is never mutated.\n */\nexport function compressCore(state: unknown, options: CompressOptions = {}): unknown {\n const opts = resolveOptions(options);\n const walked = walk(state, 0, opts, new WeakSet());\n // A top-level function/symbol compresses to undefined rather than a sentinel.\n return walked === OMIT ? undefined : walked;\n}\n","/**\n * react-context-compressor — core entry (framework-agnostic, zero dependencies).\n *\n * Mechanical compression lands here (task 002); sanitization composes into the\n * same walk in task 003.\n */\n\nimport { compressCore } from \"./core/compress\";\n\n/**\n * Options controlling how a state value is mechanically compressed and\n * sanitized into a minimal, LLM-ready payload. All fields are optional;\n * `compress(state)` with no options returns a safe deep copy.\n */\nexport interface CompressOptions {\n /**\n * Maximum object/array depth to retain. Nodes deeper than this are replaced\n * with a `\"[Object]\"` / `\"[Array]\"` marker. Default: `100` (a safe cap that\n * keeps payloads minimal and prevents stack overflow on pathologically deep\n * input). Set to `Infinity` to disable depth capping.\n */\n maxDepth?: number;\n /**\n * Maximum array length to retain. Longer arrays are truncated and a\n * `\"[+N more]\"` marker is appended. Default: unlimited.\n */\n maxArrayLength?: number;\n /** Keys (exact strings or patterns) to strip from the output. */\n strip?: Array<string | RegExp>;\n /** When true, drop `null` / `undefined` / `\"\"` / `[]` / `{}` values. */\n dropEmpty?: boolean;\n /**\n * Extra sensitive field-name matchers to redact/remove, IN ADDITION to the\n * built-in deny-list (unless {@link CompressOptions.defaultSanitize} is\n * `false`). A `string` matches a key case-insensitively and exactly; a\n * `RegExp` matches by pattern. Matching is by field NAME, not value.\n */\n sanitize?: Array<string | RegExp>;\n /**\n * Apply the built-in sensitive-field deny-list (password, token, secret,\n * apiKey, authorization, cookie, ssn, creditCard, …). Default: `true`.\n * Set `false` to rely solely on {@link CompressOptions.sanitize}.\n */\n defaultSanitize?: boolean;\n /**\n * How to treat a sensitive field: `\"redact\"` replaces its value with\n * {@link CompressOptions.redactedValue}; `\"remove\"` drops the key entirely.\n * Either way the value is never read or emitted. Default: `\"redact\"`.\n */\n sanitizeMode?: \"redact\" | \"remove\";\n /** Replacement used when `sanitizeMode` is `\"redact\"`. Default: `\"[REDACTED]\"`. */\n redactedValue?: string;\n}\n\n/**\n * Mechanically compress and sanitize a state value into a minimal, safe payload.\n *\n * Pure and deterministic: the same input and options always produce the same\n * output, and the input is never mutated. Performs no I/O — purely structural.\n */\nexport function compress<T>(state: T, options: CompressOptions = {}): unknown {\n return compressCore(state, options);\n}\n"]}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
package/dist/react/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/sanitize.ts","../../src/core/compress.ts","../../src/index.ts","../../src/react/signature.ts","../../src/react/index.ts"],"names":["out","useMemo"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Client-side data-safety layer — detect sensitive field names so the walker\n * can redact or remove them BEFORE their values are ever read. Mechanical and\n * key-name-driven: no value inspection, no network, no models.\n *\n * Limitations (key-name-driven by design):\n * - Matches field NAMES, not values: a secret under an innocuous key, or as a\n * bare array/Set element (no key), is NOT detected.\n * - Only own enumerable string keys are processed; Symbol-keyed / non-enumerable\n * properties are dropped by the walker, never scanned.\n * - Homoglyph attacks (e.g. Cyrillic look-alikes) are not fully closed; keys are\n * NFKC-normalized and zero-width-stripped before matching, which defeats the\n * common fullwidth/zero-width evasions but not deliberate confusables.\n */\n\n/** Default replacement value used when a sensitive field is redacted. */\nexport const REDACTED = \"[REDACTED]\";\n\n/**\n * Built-in deny-list of sensitive field-name patterns (case-insensitive).\n * Patterns are anchored to avoid false positives on common keys — e.g. it does\n * NOT redact `author`, `dashboard`, `secretary`, `tokenCount`, `promptTokens`,\n * or `accessKeyboard`.\n */\nexport const DEFAULT_DENY_LIST: readonly RegExp[] = [\n // Passwords / pass-phrases\n /pass(?:word|wd|phrase)/i,\n // Secrets (\"secret\", \"clientSecret\", \"secretKey\") but not \"secretary\"\n /secret(?!ary)/i,\n // Tokens — standalone or with an auth-ish prefix; NOT tokenCount/tokenize/promptTokens\n /\\btoken\\b/i,\n /(?:access|refresh|id|auth|bearer|api|csrf|xsrf|session|sso|oauth|reset|verification|activation)[-_]?token/i,\n // Keys (\"apiKey\", \"accessKey\", \"signingKey\") but not \"accessKeyword\"/\"keyboard\"\n /(?:api|access|private|signing|encryption)[-_]?keys?(?![a-z])/i,\n /\\bjwt\\b/i,\n /authorization/i,\n /bearer/i,\n /credentials?/i,\n /cookie/i,\n /session[-_]?(?:id|token|key)/i,\n // 2FA / recovery\n /\\b(?:otp|totp|mfa|2fa)\\b/i,\n /(?:recovery|backup)[-_]?codes?/i,\n /\\bmnemonic\\b/i,\n /seed[-_]?phrase/i,\n // Crypto / signing\n /\\bhmac\\b/i,\n /\\bsignature\\b/i,\n /\\b[cx]srf\\b/i,\n // Connection strings / DB credentials\n /connection[-_]?string/i,\n /\\bdsn\\b/i,\n /(?:database|db)[-_]?(?:url|uri)/i,\n // PII / financial\n /ssn/i,\n /social[-_]?security[-_]?(?:number|no)?/i,\n /credit[-_]?card/i,\n /card[-_]?number/i,\n /cvv2?/i,\n /\\bpin\\b/i,\n /\\biban\\b/i,\n /routing[-_]?number/i,\n /account[-_]?number/i,\n /\\bpassport\\b/i,\n /tax[-_]?id/i,\n];\n\n/** Zero-width / default-ignorable code points used to evade name matching. */\nconst ZERO_WIDTH = /[\\u00AD\\u200B-\\u200D\\u2060\\uFEFF]/g;\n\n/**\n * Normalize a key before matching: NFKC folds fullwidth/compatibility forms to\n * their ASCII equivalents, and zero-width characters are stripped. This defeats\n * the common `\"password\"` / zero-width-injected evasions.\n */\nexport function normalizeKey(key: string): string {\n return key.normalize(\"NFKC\").replace(ZERO_WIDTH, \"\");\n}\n\n/**\n * Stateless RegExp test. A user-supplied pattern with the `g`/`y` flag carries\n * a mutable `lastIndex`; resetting it keeps matching deterministic across keys.\n */\nexport function regexTest(re: RegExp, value: string): boolean {\n if (re.global || re.sticky) re.lastIndex = 0;\n return re.test(value);\n}\n\n/**\n * Is `key` a sensitive field name? Checks user matchers (string = exact,\n * case-insensitive; RegExp = pattern) and, when `useDefaults`, the built-in\n * deny-list. Keys are NFKC-normalized and zero-width-stripped first.\n */\nexport function isSensitiveKey(\n key: string,\n userMatchers: ReadonlyArray<string | RegExp>,\n useDefaults: boolean,\n): boolean {\n const normalized = normalizeKey(key);\n const lower = normalized.toLowerCase();\n for (const matcher of userMatchers) {\n if (typeof matcher === \"string\") {\n if (normalizeKey(matcher).toLowerCase() === lower) return true;\n } else if (regexTest(matcher, normalized)) {\n return true;\n }\n }\n if (useDefaults) {\n for (const re of DEFAULT_DENY_LIST) {\n if (regexTest(re, normalized)) return true;\n }\n }\n return false;\n}\n","/**\n * Core compression walker — mechanical, deterministic, zero-dependency.\n *\n * A single recursive pass applies: key stripping, depth capping, array-length\n * capping, and empty-value dropping, with circular-reference protection.\n * Sanitization (task 003) composes into this same walk via {@link CompressOptions.sanitize}.\n */\n\nimport type { CompressOptions } from \"../index\";\nimport { isSensitiveKey, REDACTED, regexTest } from \"./sanitize\";\n\n/** Marker substituted for a node that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_OBJECT = \"[Object]\";\n/** Marker substituted for an array that exceeds {@link CompressOptions.maxDepth}. */\nexport const TRUNCATED_ARRAY = \"[Array]\";\n/** Marker substituted for a circular back-reference. */\nexport const CIRCULAR = \"[Circular]\";\n/** Marker substituted for a property whose getter threw when read. */\nexport const GETTER_ERROR = \"[Getter]\";\n\n/**\n * Safe default depth cap. Real application state is rarely deeper than a few\n * dozen levels; capping by default keeps payloads minimal and prevents a\n * stack-overflow DoS on pathologically deep (untrusted) input. Opt out with\n * `maxDepth: Infinity`.\n */\nexport const DEFAULT_MAX_DEPTH = 100;\n\n/** Fully-resolved options after defaults are applied. */\ninterface ResolvedOptions {\n maxDepth: number;\n maxArrayLength: number;\n strip: Array<string | RegExp>;\n dropEmpty: boolean;\n sanitize: Array<string | RegExp>;\n defaultSanitize: boolean;\n sanitizeMode: \"redact\" | \"remove\";\n redactedValue: string;\n}\n\nconst DEFAULTS: ResolvedOptions = {\n maxDepth: DEFAULT_MAX_DEPTH,\n maxArrayLength: Number.POSITIVE_INFINITY,\n strip: [],\n dropEmpty: false,\n sanitize: [],\n defaultSanitize: true,\n sanitizeMode: \"redact\",\n redactedValue: REDACTED,\n};\n\n/** A unique sentinel meaning \"this value should be omitted from the output\". */\nconst OMIT = Symbol(\"omit\");\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Assign an own, enumerable data property — even for dangerous keys like\n * `__proto__` (a plain `out[key] = v` would reassign the prototype instead of\n * creating a property, corrupting the output and silently dropping the value).\n */\nfunction safeAssign(target: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(target, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n}\n\n/** Does `key` match any matcher (exact string or RegExp test)? */\nexport function keyMatches(key: string, matchers: ReadonlyArray<string | RegExp>): boolean {\n for (const matcher of matchers) {\n if (typeof matcher === \"string\") {\n if (matcher === key) return true;\n } else if (regexTest(matcher, key)) {\n return true;\n }\n }\n return false;\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined || value === \"\") return true;\n if (Array.isArray(value)) return value.length === 0;\n if (isPlainObject(value)) return Object.keys(value).length === 0;\n return false;\n}\n\n/**\n * Resolve user options against defaults. Exposed so the React layer and\n * sanitization can share one normalization path.\n */\nexport function resolveOptions(options: CompressOptions): ResolvedOptions {\n return {\n maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,\n maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,\n strip: options.strip ?? DEFAULTS.strip,\n dropEmpty: options.dropEmpty ?? DEFAULTS.dropEmpty,\n sanitize: options.sanitize ?? DEFAULTS.sanitize,\n defaultSanitize: options.defaultSanitize ?? DEFAULTS.defaultSanitize,\n sanitizeMode: options.sanitizeMode ?? DEFAULTS.sanitizeMode,\n redactedValue: options.redactedValue ?? DEFAULTS.redactedValue,\n };\n}\n\n/** Walk own enumerable string keys of an object-like value into `out`. */\nfunction walkObjectInto(\n out: Record<string, unknown>,\n source: Record<string, unknown>,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): void {\n for (const key of Object.keys(source)) {\n if (keyMatches(key, opts.strip)) continue;\n // Sanitize BEFORE reading the value: a sensitive value is never read\n // (no getter fired), never walked, and never reaches the output.\n if (isSensitiveKey(key, opts.sanitize, opts.defaultSanitize)) {\n if (opts.sanitizeMode === \"remove\") continue;\n safeAssign(out, key, opts.redactedValue);\n continue;\n }\n let raw: unknown;\n try {\n raw = source[key];\n } catch {\n // A getter threw — degrade to a marker rather than crashing the walk.\n safeAssign(out, key, GETTER_ERROR);\n continue;\n }\n const walked = walk(raw, depth + 1, opts, seen);\n if (walked === OMIT) continue;\n if (opts.dropEmpty && isEmptyValue(walked)) continue;\n safeAssign(out, key, walked);\n }\n}\n\nfunction walk(\n value: unknown,\n depth: number,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n): unknown {\n if (value === null) return null;\n const type = typeof value;\n if (type === \"function\" || type === \"symbol\") return OMIT;\n // BigInt is not JSON-serializable; coerce to string for an LLM-ready payload.\n if (type === \"bigint\") return (value as bigint).toString();\n if (type !== \"object\") return value;\n\n // Circular-reference guard sits above all object normalization so a cycle\n // through a Map/Set is caught instead of overflowing the stack.\n const ref = value as object;\n if (seen.has(ref)) return CIRCULAR;\n\n // Non-plain objects we deliberately normalize for a predictable payload.\n if (value instanceof Date) return value;\n if (value instanceof Map) {\n seen.add(ref);\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value) safeAssign(obj, String(k), v);\n const result = walk(obj, depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n if (value instanceof Set) {\n seen.add(ref);\n const result = walk(Array.from(value), depth, opts, seen);\n seen.delete(ref);\n return result;\n }\n\n if (Array.isArray(value)) {\n if (depth >= opts.maxDepth) return TRUNCATED_ARRAY;\n seen.add(ref);\n const limited =\n value.length > opts.maxArrayLength ? value.slice(0, opts.maxArrayLength) : value;\n const out: unknown[] = [];\n for (const item of limited) {\n const walked = walk(item, depth + 1, opts, seen);\n // In arrays, an omitted item collapses to null to preserve index intent.\n out.push(walked === OMIT ? null : walked);\n }\n if (value.length > opts.maxArrayLength) {\n out.push(`[+${value.length - opts.maxArrayLength} more]`);\n }\n seen.delete(ref);\n return out;\n }\n\n // Plain objects and unknown object kinds (class instances) both rebuild as a\n // plain object from their own enumerable string keys.\n if (depth >= opts.maxDepth) return TRUNCATED_OBJECT;\n seen.add(ref);\n const out: Record<string, unknown> = {};\n walkObjectInto(out, value as Record<string, unknown>, depth, opts, seen);\n seen.delete(ref);\n return out;\n}\n\n/**\n * Mechanically compress a state value into a minimal payload, applying the\n * structural transforms in {@link CompressOptions}. Pure and deterministic;\n * the input is never mutated.\n */\nexport function compressCore(state: unknown, options: CompressOptions = {}): unknown {\n const opts = resolveOptions(options);\n const walked = walk(state, 0, opts, new WeakSet());\n // A top-level function/symbol compresses to undefined rather than a sentinel.\n return walked === OMIT ? undefined : walked;\n}\n","/**\n * react-context-compressor — core entry (framework-agnostic, zero dependencies).\n *\n * Mechanical compression lands here (task 002); sanitization composes into the\n * same walk in task 003.\n */\n\nimport { compressCore } from \"./core/compress\";\n\n/**\n * Options controlling how a state value is mechanically compressed and\n * sanitized into a minimal, LLM-ready payload. All fields are optional;\n * `compress(state)` with no options returns a safe deep copy.\n */\nexport interface CompressOptions {\n /**\n * Maximum object/array depth to retain. Nodes deeper than this are replaced\n * with a `\"[Object]\"` / `\"[Array]\"` marker. Default: `100` (a safe cap that\n * keeps payloads minimal and prevents stack overflow on pathologically deep\n * input). Set to `Infinity` to disable depth capping.\n */\n maxDepth?: number;\n /**\n * Maximum array length to retain. Longer arrays are truncated and a\n * `\"[+N more]\"` marker is appended. Default: unlimited.\n */\n maxArrayLength?: number;\n /** Keys (exact strings or patterns) to strip from the output. */\n strip?: Array<string | RegExp>;\n /** When true, drop `null` / `undefined` / `\"\"` / `[]` / `{}` values. */\n dropEmpty?: boolean;\n /**\n * Extra sensitive field-name matchers to redact/remove, IN ADDITION to the\n * built-in deny-list (unless {@link CompressOptions.defaultSanitize} is\n * `false`). A `string` matches a key case-insensitively and exactly; a\n * `RegExp` matches by pattern. Matching is by field NAME, not value.\n */\n sanitize?: Array<string | RegExp>;\n /**\n * Apply the built-in sensitive-field deny-list (password, token, secret,\n * apiKey, authorization, cookie, ssn, creditCard, …). Default: `true`.\n * Set `false` to rely solely on {@link CompressOptions.sanitize}.\n */\n defaultSanitize?: boolean;\n /**\n * How to treat a sensitive field: `\"redact\"` replaces its value with\n * {@link CompressOptions.redactedValue}; `\"remove\"` drops the key entirely.\n * Either way the value is never read or emitted. Default: `\"redact\"`.\n */\n sanitizeMode?: \"redact\" | \"remove\";\n /** Replacement used when `sanitizeMode` is `\"redact\"`. Default: `\"[REDACTED]\"`. */\n redactedValue?: string;\n}\n\n/**\n * Mechanically compress and sanitize a state value into a minimal, safe payload.\n *\n * Pure and deterministic: the same input and options always produce the same\n * output, and the input is never mutated. Performs no I/O — purely structural.\n */\nexport function compress<T>(state: T, options: CompressOptions = {}): unknown {\n return compressCore(state, options);\n}\n","/**\n * Internal helper for the React hook: turn a {@link CompressOptions} object into\n * a stable string signature so an inline options literal (new reference every\n * render) doesn't thrash `useMemo`. Not part of the public `./react` API.\n */\nimport type { CompressOptions } from \"../index\";\n\n// Control-char delimiters that cannot appear unescaped in a JSON-encoded token,\n// so neither matcher contents nor free-text option values can forge a boundary.\nconst FIELD = \"\\u0001\";\nconst ITEM = \"\\u0000\";\n\n/** Unambiguous, collision-free key for one matcher (string or RegExp). */\nfunction matcherKey(m: string | RegExp): string {\n return typeof m === \"string\"\n ? `s:${JSON.stringify(m)}`\n : `r:${JSON.stringify(m.source)}:${m.flags}`;\n}\n\n/**\n * Build a stable signature from an options object. Two options objects with\n * equal content produce the same signature; any content difference (including\n * RegExp flags, matcher order, or `Infinity` vs unset) produces a different one.\n */\nexport function optionsSignature(o: CompressOptions): string {\n return [\n `d=${o.maxDepth ?? \"\"}`,\n `a=${o.maxArrayLength ?? \"\"}`,\n `e=${o.dropEmpty ?? \"\"}`,\n `ds=${o.defaultSanitize ?? \"\"}`,\n `sm=${o.sanitizeMode ?? \"\"}`,\n `rv=${JSON.stringify(o.redactedValue ?? \"\")}`,\n `st=${(o.strip ?? []).map(matcherKey).join(ITEM)}`,\n `sn=${(o.sanitize ?? []).map(matcherKey).join(ITEM)}`,\n ].join(FIELD);\n}\n","/**\n * react-context-compressor — React bindings entry.\n *\n * Imports only from the core entry and the `react` peer dependency, so the\n * core (`.`) entry stays React-free and the React layer tree-shakes away for\n * consumers who import only `.`.\n */\nimport { useMemo } from \"react\";\nimport { type CompressOptions, compress } from \"../index\";\nimport { optionsSignature } from \"./signature\";\n\n/**\n * React hook returning a memoized, compressed + sanitized view of `state`.\n *\n * Recomputes only when the `state` reference changes or the options content\n * changes (compared by a stable signature, so an inline options literal is\n * fine). Pure: no DOM access, no side effects — safe under SSR and React\n * Server Components. Works on React 17 / 18 / 19.\n */\nexport function useCompressedContext<T>(state: T, options: CompressOptions = {}): unknown {\n const signature = optionsSignature(options);\n // biome-ignore lint/correctness/useExhaustiveDependencies: `options` is keyed by its stable `signature`\n return useMemo(() => compress(state, options), [state, signature]);\n}\n\nexport type { CompressOptions };\n"]}
|
package/dist/react/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/signature.ts","../../src/react/index.ts"],"names":[],"mappings":";;;;AASA,IAAM,KAAA,GAAQ,GAAA;AACd,IAAM,IAAA,GAAO,IAAA;AAGb,SAAS,WAAW,CAAA,EAA4B;AAC9C,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,GAChB,CAAA,EAAA,EAAK,IAAA,CAAK,UAAU,CAAC,CAAC,CAAA,CAAA,GACtB,CAAA,EAAA,EAAK,KAAK,SAAA,CAAU,CAAA,CAAE,MAAM,CAAC,CAAA,CAAA,EAAI,EAAE,KAAK,CAAA,CAAA;AAC9C;AAOO,SAAS,iBAAiB,CAAA,EAA4B;AAC3D,EAAA,OAAO;AAAA,IACL,CAAA,EAAA,EAAK,CAAA,CAAE,QAAA,IAAY,EAAE,CAAA,CAAA;AAAA,IACrB,CAAA,EAAA,EAAK,CAAA,CAAE,cAAA,IAAkB,EAAE,CAAA,CAAA;AAAA,IAC3B,CAAA,EAAA,EAAK,CAAA,CAAE,SAAA,IAAa,EAAE,CAAA,CAAA;AAAA,IACtB,CAAA,GAAA,EAAM,CAAA,CAAE,eAAA,IAAmB,EAAE,CAAA,CAAA;AAAA,IAC7B,CAAA,GAAA,EAAM,CAAA,CAAE,YAAA,IAAgB,EAAE,CAAA,CAAA;AAAA,IAC1B,MAAM,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,aAAA,IAAiB,EAAE,CAAC,CAAA,CAAA;AAAA,IAC3C,CAAA,GAAA,EAAA,CAAO,CAAA,CAAE,KAAA,IAAS,EAAC,EAAG,IAAI,UAAU,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,IAChD,CAAA,GAAA,EAAA,CAAO,CAAA,CAAE,QAAA,IAAY,EAAC,EAAG,IAAI,UAAU,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,GACrD,CAAE,KAAK,KAAK,CAAA;AACd;;;AChBO,SAAS,oBAAA,CAAwB,KAAA,EAAU,OAAA,GAA2B,EAAC,EAAY;AACxF,EAAA,MAAM,SAAA,GAAY,iBAAiB,OAAO,CAAA;AAE1C,EAAA,OAAO,OAAA,CAAQ,MAAM,QAAA,CAAS,KAAA,EAAO,OAAO,CAAA,EAAG,CAAC,KAAA,EAAO,SAAS,CAAC,CAAA;AACnE","file":"index.mjs","sourcesContent":["/**\n * Internal helper for the React hook: turn a {@link CompressOptions} object into\n * a stable string signature so an inline options literal (new reference every\n * render) doesn't thrash `useMemo`. Not part of the public `./react` API.\n */\nimport type { CompressOptions } from \"../index\";\n\n// Control-char delimiters that cannot appear unescaped in a JSON-encoded token,\n// so neither matcher contents nor free-text option values can forge a boundary.\nconst FIELD = \"\\u0001\";\nconst ITEM = \"\\u0000\";\n\n/** Unambiguous, collision-free key for one matcher (string or RegExp). */\nfunction matcherKey(m: string | RegExp): string {\n return typeof m === \"string\"\n ? `s:${JSON.stringify(m)}`\n : `r:${JSON.stringify(m.source)}:${m.flags}`;\n}\n\n/**\n * Build a stable signature from an options object. Two options objects with\n * equal content produce the same signature; any content difference (including\n * RegExp flags, matcher order, or `Infinity` vs unset) produces a different one.\n */\nexport function optionsSignature(o: CompressOptions): string {\n return [\n `d=${o.maxDepth ?? \"\"}`,\n `a=${o.maxArrayLength ?? \"\"}`,\n `e=${o.dropEmpty ?? \"\"}`,\n `ds=${o.defaultSanitize ?? \"\"}`,\n `sm=${o.sanitizeMode ?? \"\"}`,\n `rv=${JSON.stringify(o.redactedValue ?? \"\")}`,\n `st=${(o.strip ?? []).map(matcherKey).join(ITEM)}`,\n `sn=${(o.sanitize ?? []).map(matcherKey).join(ITEM)}`,\n ].join(FIELD);\n}\n","/**\n * react-context-compressor — React bindings entry.\n *\n * Imports only from the core entry and the `react` peer dependency, so the\n * core (`.`) entry stays React-free and the React layer tree-shakes away for\n * consumers who import only `.`.\n */\nimport { useMemo } from \"react\";\nimport { type CompressOptions, compress } from \"../index\";\nimport { optionsSignature } from \"./signature\";\n\n/**\n * React hook returning a memoized, compressed + sanitized view of `state`.\n *\n * Recomputes only when the `state` reference changes or the options content\n * changes (compared by a stable signature, so an inline options literal is\n * fine). Pure: no DOM access, no side effects — safe under SSR and React\n * Server Components. Works on React 17 / 18 / 19.\n */\nexport function useCompressedContext<T>(state: T, options: CompressOptions = {}): unknown {\n const signature = optionsSignature(options);\n // biome-ignore lint/correctness/useExhaustiveDependencies: `options` is keyed by its stable `signature`\n return useMemo(() => compress(state, options), [state, signature]);\n}\n\nexport type { CompressOptions };\n"]}
|