@oh-my-pi/pi-utils 18.4.1 → 18.4.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.4.2] - 2026-09-28
6
+
7
+ ### Added
8
+
9
+ - Added cloneJsonTree to provide a high-performance deep copy utility for JSON-shaped object trees
10
+
5
11
  ## [18.4.1] - 2026-09-28
6
12
 
7
13
  ### Fixed
@@ -43,3 +43,15 @@ export * from "./version.js";
43
43
  export * from "./which.js";
44
44
  export * from "./yaml-config.js";
45
45
  export declare function structuredCloneJSON<T>(value: T): T;
46
+ /**
47
+ * Deep-copies a JSON-shaped tree (arrays and plain or null-prototype objects)
48
+ * so later mutation of the source can never reach the copy. Strings and other
49
+ * primitives are immutable and shared, so the cost is O(containers) instead of
50
+ * `structuredClone`'s O(bytes) — the difference matters for hot paths that
51
+ * re-copy large string payloads (e.g. streamed tool-call arguments) per delta.
52
+ *
53
+ * Copies own enumerable string keys only. Any other object (class instance,
54
+ * Date, Map, inherited-prototype object) is copied with {@link structuredCloneJSON}.
55
+ * Not cycle-safe: callers MUST pass acyclic data, as any JSON-serializable value is.
56
+ */
57
+ export declare function cloneJsonTree<T>(value: T): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-utils",
3
- "version": "18.4.1",
3
+ "version": "18.4.2",
4
4
  "description": "Shared utilities for pi packages",
5
5
  "keywords": [
6
6
  "cli",
@@ -57,7 +57,7 @@
57
57
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
58
58
  },
59
59
  "dependencies": {
60
- "@oh-my-pi/pi-natives": "18.4.1"
60
+ "@oh-my-pi/pi-natives": "18.4.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/bun": "^1.3.14"
package/src/index.ts CHANGED
@@ -63,3 +63,42 @@ export function structuredCloneJSON<T>(value: T): T {
63
63
  }
64
64
  return JSON.parse(JSON.stringify(value)) as T;
65
65
  }
66
+
67
+ /**
68
+ * Deep-copies a JSON-shaped tree (arrays and plain or null-prototype objects)
69
+ * so later mutation of the source can never reach the copy. Strings and other
70
+ * primitives are immutable and shared, so the cost is O(containers) instead of
71
+ * `structuredClone`'s O(bytes) — the difference matters for hot paths that
72
+ * re-copy large string payloads (e.g. streamed tool-call arguments) per delta.
73
+ *
74
+ * Copies own enumerable string keys only. Any other object (class instance,
75
+ * Date, Map, inherited-prototype object) is copied with {@link structuredCloneJSON}.
76
+ * Not cycle-safe: callers MUST pass acyclic data, as any JSON-serializable value is.
77
+ */
78
+ export function cloneJsonTree<T>(value: T): T {
79
+ return cloneJsonNode(value) as T;
80
+ }
81
+
82
+ function isJsonRecord(value: object): value is Record<string, unknown> {
83
+ const proto = Object.getPrototypeOf(value);
84
+ return proto === Object.prototype || proto === null;
85
+ }
86
+
87
+ function cloneJsonNode(value: unknown): unknown {
88
+ if (value === null || typeof value !== "object") return value;
89
+ if (Array.isArray(value)) return value.map(cloneJsonNode);
90
+ if (!isJsonRecord(value)) return structuredCloneJSON(value);
91
+ const out: Record<string, unknown> = {};
92
+ for (const key in value) {
93
+ if (!Object.hasOwn(value, key)) continue;
94
+ const child = cloneJsonNode(value[key]);
95
+ // Assigning `__proto__` would swap the copy's prototype instead of
96
+ // creating the own data property JSON.parse produces.
97
+ if (key === "__proto__") {
98
+ Object.defineProperty(out, key, { value: child, enumerable: true, writable: true, configurable: true });
99
+ } else {
100
+ out[key] = child;
101
+ }
102
+ }
103
+ return out;
104
+ }