@sohqureshi/tokenwise 1.0.2 → 1.0.4

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 CHANGED
@@ -33,13 +33,13 @@ Raw JSON is:
33
33
  ## Installation
34
34
 
35
35
  ```bash
36
- npm install tokenwise
36
+ npm install @sohqureshi/tokenwise
37
37
  ```
38
38
 
39
39
  ## Quick Usage
40
40
 
41
41
  ```js
42
- import ai, { compact, flatten, toNatural, toTOON } from "tokenwise";
42
+ import ai, { compact, flatten, toNatural, toTOON } from "@sohqureshi/tokenwise";
43
43
 
44
44
  const product = {
45
45
  product: {
@@ -208,14 +208,16 @@ LLMs charge and reason over tokens. Sending raw JSON often includes repeated key
208
208
  node demo.js
209
209
  ```
210
210
 
211
- You can visualize token optimization results using planned CLI/Web visual tools.
211
+ Use `--analyze` to compare serialized input and output with TokenWise's
212
+ four-characters-per-token heuristic. This is an estimate, not a replacement
213
+ for a model-specific tokenizer or a provider's billed token count.
212
214
 
213
215
  ---
214
216
 
215
217
  ## 🚀 Roadmap
216
218
 
217
- * [x] CLI support *(Coming Soon)*
218
- * [ ] NPM Support
219
+ * [x] CLI support
220
+ * [x] NPM Support
219
221
  * [ ] Streaming support (GB+ data)
220
222
  * [ ] Schema-aware optimization
221
223
  * [ ] SaaS API
@@ -259,4 +261,4 @@ If it helps you, consider supporting its development:
259
261
 
260
262
  ## 💡 Vision
261
263
 
262
- Make AI cheaper and faster by optimizing data before it reaches the model.
264
+ Make AI cheaper and faster by optimizing data before it reaches the model.
@@ -14,11 +14,11 @@ function toTOON(data, indent = 0) {
14
14
  }
15
15
  return result;
16
16
  }
17
- return `${space}[${data.length}]: ${data.join(",")}`;
17
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
18
18
  }
19
19
  if (typeof data === "object" && data !== null) {
20
20
  let result = "";
21
- for (const key in data) {
21
+ for (const key of Object.keys(data)) {
22
22
  const value = data[key];
23
23
  if (typeof value === "object" && value !== null) {
24
24
  result += `${space}${key}:
@@ -37,7 +37,9 @@ ${toTOON(value, indent + 1)}
37
37
  }
38
38
  function formatValue(val) {
39
39
  if (val === null || val === void 0) return "";
40
- if (typeof val === "string") return val;
40
+ if (typeof val === "string") {
41
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
42
+ }
41
43
  return String(val);
42
44
  }
43
45
  function isPlainObject(value) {
@@ -0,0 +1,14 @@
1
+ // src/core/token.ts
2
+ function serializeForTokenEstimate(value) {
3
+ if (typeof value === "string") return value;
4
+ const serialized = JSON.stringify(value);
5
+ return serialized ?? "";
6
+ }
7
+ function estimateTokens(value) {
8
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
9
+ }
10
+
11
+ export {
12
+ serializeForTokenEstimate,
13
+ estimateTokens
14
+ };
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  analyze
3
- } from "./chunk-GQ62V6ZK.js";
3
+ } from "./chunk-ZVQRYJU5.js";
4
4
  import {
5
5
  toTOON
6
- } from "./chunk-3ECVBELU.js";
6
+ } from "./chunk-6VRLDRJK.js";
7
7
  import {
8
8
  compact
9
- } from "./chunk-EUFLW42L.js";
9
+ } from "./chunk-XE36GLJP.js";
10
10
  import {
11
11
  flatten
12
- } from "./chunk-YMOSWQGE.js";
12
+ } from "./chunk-L7BC62MT.js";
13
13
  import {
14
14
  toNatural
15
15
  } from "./chunk-D4CFTFM3.js";
16
16
  import {
17
17
  prune
18
- } from "./chunk-IAV75SZA.js";
18
+ } from "./chunk-ZD536GZF.js";
19
19
 
20
20
  // src/chain.ts
21
21
  var AIChain = class {
@@ -2,20 +2,28 @@
2
2
  function flatten(obj, prefix = "", res = {}) {
3
3
  if (obj === null || obj === void 0) return res;
4
4
  if (typeof obj !== "object") {
5
- res[prefix] = obj;
5
+ setValue(res, prefix, obj);
6
6
  return res;
7
7
  }
8
- for (const key in obj) {
8
+ for (const key of Object.keys(obj)) {
9
9
  const value = obj[key];
10
10
  const newKey = prefix ? `${prefix}.${key}` : key;
11
11
  if (typeof value === "object" && value !== null) {
12
12
  flatten(value, newKey, res);
13
13
  } else {
14
- res[newKey] = value;
14
+ setValue(res, newKey, value);
15
15
  }
16
16
  }
17
17
  return res;
18
18
  }
19
+ function setValue(res, key, value) {
20
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
21
+ throw new Error(
22
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
23
+ );
24
+ }
25
+ res[key] = value;
26
+ }
19
27
 
20
28
  export {
21
29
  flatten
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  prune
3
- } from "./chunk-IAV75SZA.js";
3
+ } from "./chunk-ZD536GZF.js";
4
4
 
5
5
  // src/core/compact.ts
6
6
  function compact(obj) {
@@ -19,7 +19,7 @@ function prune(obj, options = {}) {
19
19
  return arr;
20
20
  }
21
21
  const result = {};
22
- for (const key in obj) {
22
+ for (const key of Object.keys(obj)) {
23
23
  if (removeKeys.includes(key)) continue;
24
24
  const value = prune(obj[key], normalizedOptions);
25
25
  if (value === void 0) continue;
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  toTOON
3
- } from "./chunk-3ECVBELU.js";
3
+ } from "./chunk-6VRLDRJK.js";
4
4
  import {
5
5
  compact
6
- } from "./chunk-EUFLW42L.js";
6
+ } from "./chunk-XE36GLJP.js";
7
7
  import {
8
8
  flatten
9
- } from "./chunk-YMOSWQGE.js";
9
+ } from "./chunk-L7BC62MT.js";
10
10
  import {
11
11
  prune
12
- } from "./chunk-IAV75SZA.js";
12
+ } from "./chunk-ZD536GZF.js";
13
13
  import {
14
- estimateTokens
15
- } from "./chunk-TM6L7YF2.js";
14
+ estimateTokens,
15
+ serializeForTokenEstimate
16
+ } from "./chunk-7CL7GVRF.js";
16
17
 
17
18
  // src/core/analyze.ts
18
19
  function analyze(input, options = {}) {
@@ -23,11 +24,13 @@ function analyze(input, options = {}) {
23
24
  savings: 0,
24
25
  savingsPercent: 0,
25
26
  optimizedData: null,
26
- reductionRatio: 1
27
+ reductionRatio: 1,
28
+ originalCharacters: 0,
29
+ optimizedCharacters: 0,
30
+ estimator: "heuristic: 1 token \u2248 4 characters"
27
31
  };
28
32
  }
29
- let originalTokens = estimateTokens(input);
30
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
33
+ const originalTokens = estimateTokens(input);
31
34
  let optimizedData = input;
32
35
  if (options.prune && Array.isArray(options.prune)) {
33
36
  optimizedData = prune(optimizedData, options.prune);
@@ -41,8 +44,7 @@ function analyze(input, options = {}) {
41
44
  if (options.toTOON === true || options.toon === true) {
42
45
  optimizedData = toTOON(optimizedData);
43
46
  }
44
- let optimizedTokens = estimateTokens(optimizedData);
45
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
47
+ const optimizedTokens = estimateTokens(optimizedData);
46
48
  const savings = Math.max(0, originalTokens - optimizedTokens);
47
49
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
48
50
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
@@ -52,7 +54,10 @@ function analyze(input, options = {}) {
52
54
  savings,
53
55
  savingsPercent,
54
56
  optimizedData,
55
- reductionRatio
57
+ reductionRatio,
58
+ originalCharacters: serializeForTokenEstimate(input).length,
59
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
60
+ estimator: "heuristic: 1 token \u2248 4 characters"
56
61
  };
57
62
  }
58
63
 
package/dist/cli.cjs CHANGED
@@ -48,7 +48,7 @@ function prune(obj, options = {}) {
48
48
  return arr;
49
49
  }
50
50
  const result = {};
51
- for (const key in obj) {
51
+ for (const key of Object.keys(obj)) {
52
52
  if (removeKeys.includes(key)) continue;
53
53
  const value = prune(obj[key], normalizedOptions);
54
54
  if (value === void 0) continue;
@@ -64,20 +64,28 @@ function prune(obj, options = {}) {
64
64
  function flatten(obj, prefix = "", res = {}) {
65
65
  if (obj === null || obj === void 0) return res;
66
66
  if (typeof obj !== "object") {
67
- res[prefix] = obj;
67
+ setValue(res, prefix, obj);
68
68
  return res;
69
69
  }
70
- for (const key in obj) {
70
+ for (const key of Object.keys(obj)) {
71
71
  const value = obj[key];
72
72
  const newKey = prefix ? `${prefix}.${key}` : key;
73
73
  if (typeof value === "object" && value !== null) {
74
74
  flatten(value, newKey, res);
75
75
  } else {
76
- res[newKey] = value;
76
+ setValue(res, newKey, value);
77
77
  }
78
78
  }
79
79
  return res;
80
80
  }
81
+ function setValue(res, key, value) {
82
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
83
+ throw new Error(
84
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
85
+ );
86
+ }
87
+ res[key] = value;
88
+ }
81
89
 
82
90
  // src/core/compact.ts
83
91
  function compact(obj) {
@@ -101,11 +109,11 @@ function toTOON(data, indent = 0) {
101
109
  }
102
110
  return result;
103
111
  }
104
- return `${space}[${data.length}]: ${data.join(",")}`;
112
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
105
113
  }
106
114
  if (typeof data === "object" && data !== null) {
107
115
  let result = "";
108
- for (const key in data) {
116
+ for (const key of Object.keys(data)) {
109
117
  const value = data[key];
110
118
  if (typeof value === "object" && value !== null) {
111
119
  result += `${space}${key}:
@@ -124,7 +132,9 @@ ${toTOON(value, indent + 1)}
124
132
  }
125
133
  function formatValue(val) {
126
134
  if (val === null || val === void 0) return "";
127
- if (typeof val === "string") return val;
135
+ if (typeof val === "string") {
136
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
137
+ }
128
138
  return String(val);
129
139
  }
130
140
  function isPlainObject(value) {
@@ -132,9 +142,13 @@ function isPlainObject(value) {
132
142
  }
133
143
 
134
144
  // src/core/token.ts
135
- function estimateTokens(text) {
136
- if (!text) return 0;
137
- return Math.ceil(text.length / 4);
145
+ function serializeForTokenEstimate(value) {
146
+ if (typeof value === "string") return value;
147
+ const serialized = JSON.stringify(value);
148
+ return serialized ?? "";
149
+ }
150
+ function estimateTokens(value) {
151
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
138
152
  }
139
153
 
140
154
  // src/core/analyze.ts
@@ -146,11 +160,13 @@ function analyze(input, options = {}) {
146
160
  savings: 0,
147
161
  savingsPercent: 0,
148
162
  optimizedData: null,
149
- reductionRatio: 1
163
+ reductionRatio: 1,
164
+ originalCharacters: 0,
165
+ optimizedCharacters: 0,
166
+ estimator: "heuristic: 1 token \u2248 4 characters"
150
167
  };
151
168
  }
152
- let originalTokens = estimateTokens(input);
153
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
169
+ const originalTokens = estimateTokens(input);
154
170
  let optimizedData = input;
155
171
  if (options.prune && Array.isArray(options.prune)) {
156
172
  optimizedData = prune(optimizedData, options.prune);
@@ -164,8 +180,7 @@ function analyze(input, options = {}) {
164
180
  if (options.toTOON === true || options.toon === true) {
165
181
  optimizedData = toTOON(optimizedData);
166
182
  }
167
- let optimizedTokens = estimateTokens(optimizedData);
168
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
183
+ const optimizedTokens = estimateTokens(optimizedData);
169
184
  const savings = Math.max(0, originalTokens - optimizedTokens);
170
185
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
171
186
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
@@ -175,7 +190,10 @@ function analyze(input, options = {}) {
175
190
  savings,
176
191
  savingsPercent,
177
192
  optimizedData,
178
- reductionRatio
193
+ reductionRatio,
194
+ originalCharacters: serializeForTokenEstimate(input).length,
195
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
196
+ estimator: "heuristic: 1 token \u2248 4 characters"
179
197
  };
180
198
  }
181
199
 
@@ -323,7 +341,7 @@ Usage:
323
341
  Options:
324
342
  --toon Convert JSON to TOON format
325
343
  --compact Convert JSON to compact format
326
- --analyze Show token analysis
344
+ --analyze Show token analysis (heuristic estimate)
327
345
  `);
328
346
  process.exit(0);
329
347
  }
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AIChain
4
- } from "./chunk-2IQAGI7Q.js";
5
- import "./chunk-GQ62V6ZK.js";
6
- import "./chunk-3ECVBELU.js";
7
- import "./chunk-EUFLW42L.js";
8
- import "./chunk-YMOSWQGE.js";
4
+ } from "./chunk-HFMW3K22.js";
5
+ import "./chunk-ZVQRYJU5.js";
6
+ import "./chunk-6VRLDRJK.js";
7
+ import "./chunk-XE36GLJP.js";
8
+ import "./chunk-L7BC62MT.js";
9
9
  import "./chunk-D4CFTFM3.js";
10
- import "./chunk-IAV75SZA.js";
11
- import "./chunk-TM6L7YF2.js";
10
+ import "./chunk-ZD536GZF.js";
11
+ import "./chunk-7CL7GVRF.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import fs from "fs";
@@ -22,7 +22,7 @@ Usage:
22
22
  Options:
23
23
  --toon Convert JSON to TOON format
24
24
  --compact Convert JSON to compact format
25
- --analyze Show token analysis
25
+ --analyze Show token analysis (heuristic estimate)
26
26
  `);
27
27
  process.exit(0);
28
28
  }
@@ -45,7 +45,7 @@ function prune(obj, options = {}) {
45
45
  return arr;
46
46
  }
47
47
  const result = {};
48
- for (const key in obj) {
48
+ for (const key of Object.keys(obj)) {
49
49
  if (removeKeys.includes(key)) continue;
50
50
  const value = prune(obj[key], normalizedOptions);
51
51
  if (value === void 0) continue;
@@ -67,20 +67,28 @@ function compact(obj) {
67
67
  function flatten(obj, prefix = "", res = {}) {
68
68
  if (obj === null || obj === void 0) return res;
69
69
  if (typeof obj !== "object") {
70
- res[prefix] = obj;
70
+ setValue(res, prefix, obj);
71
71
  return res;
72
72
  }
73
- for (const key in obj) {
73
+ for (const key of Object.keys(obj)) {
74
74
  const value = obj[key];
75
75
  const newKey = prefix ? `${prefix}.${key}` : key;
76
76
  if (typeof value === "object" && value !== null) {
77
77
  flatten(value, newKey, res);
78
78
  } else {
79
- res[newKey] = value;
79
+ setValue(res, newKey, value);
80
80
  }
81
81
  }
82
82
  return res;
83
83
  }
84
+ function setValue(res, key, value) {
85
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
86
+ throw new Error(
87
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
88
+ );
89
+ }
90
+ res[key] = value;
91
+ }
84
92
 
85
93
  // src/core/toon.ts
86
94
  function toTOON(data, indent = 0) {
@@ -98,11 +106,11 @@ function toTOON(data, indent = 0) {
98
106
  }
99
107
  return result;
100
108
  }
101
- return `${space}[${data.length}]: ${data.join(",")}`;
109
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
102
110
  }
103
111
  if (typeof data === "object" && data !== null) {
104
112
  let result = "";
105
- for (const key in data) {
113
+ for (const key of Object.keys(data)) {
106
114
  const value = data[key];
107
115
  if (typeof value === "object" && value !== null) {
108
116
  result += `${space}${key}:
@@ -121,7 +129,9 @@ ${toTOON(value, indent + 1)}
121
129
  }
122
130
  function formatValue(val) {
123
131
  if (val === null || val === void 0) return "";
124
- if (typeof val === "string") return val;
132
+ if (typeof val === "string") {
133
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
134
+ }
125
135
  return String(val);
126
136
  }
127
137
  function isPlainObject(value) {
@@ -129,9 +139,13 @@ function isPlainObject(value) {
129
139
  }
130
140
 
131
141
  // src/core/token.ts
132
- function estimateTokens(text) {
133
- if (!text) return 0;
134
- return Math.ceil(text.length / 4);
142
+ function serializeForTokenEstimate(value) {
143
+ if (typeof value === "string") return value;
144
+ const serialized = JSON.stringify(value);
145
+ return serialized ?? "";
146
+ }
147
+ function estimateTokens(value) {
148
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
135
149
  }
136
150
 
137
151
  // src/core/analyze.ts
@@ -143,11 +157,13 @@ function analyze(input, options = {}) {
143
157
  savings: 0,
144
158
  savingsPercent: 0,
145
159
  optimizedData: null,
146
- reductionRatio: 1
160
+ reductionRatio: 1,
161
+ originalCharacters: 0,
162
+ optimizedCharacters: 0,
163
+ estimator: "heuristic: 1 token \u2248 4 characters"
147
164
  };
148
165
  }
149
- let originalTokens = estimateTokens(input);
150
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
166
+ const originalTokens = estimateTokens(input);
151
167
  let optimizedData = input;
152
168
  if (options.prune && Array.isArray(options.prune)) {
153
169
  optimizedData = prune(optimizedData, options.prune);
@@ -161,8 +177,7 @@ function analyze(input, options = {}) {
161
177
  if (options.toTOON === true || options.toon === true) {
162
178
  optimizedData = toTOON(optimizedData);
163
179
  }
164
- let optimizedTokens = estimateTokens(optimizedData);
165
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
180
+ const optimizedTokens = estimateTokens(optimizedData);
166
181
  const savings = Math.max(0, originalTokens - optimizedTokens);
167
182
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
168
183
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
@@ -172,7 +187,10 @@ function analyze(input, options = {}) {
172
187
  savings,
173
188
  savingsPercent,
174
189
  optimizedData,
175
- reductionRatio
190
+ reductionRatio,
191
+ originalCharacters: serializeForTokenEstimate(input).length,
192
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
193
+ estimator: "heuristic: 1 token \u2248 4 characters"
176
194
  };
177
195
  }
178
196
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,13 +1,23 @@
1
1
  /**
2
2
  * Safe version of analyze() - Prevents NaN values
3
3
  */
4
- declare function analyze(input: any, options?: any): {
4
+ type AnalyzeOptions = {
5
+ prune?: string[];
6
+ compact?: boolean;
7
+ flatten?: boolean;
8
+ toTOON?: boolean;
9
+ toon?: boolean;
10
+ };
11
+ declare function analyze(input: unknown, options?: AnalyzeOptions): {
5
12
  originalTokens: number;
6
13
  optimizedTokens: number;
7
14
  savings: number;
8
15
  savingsPercent: number;
9
- optimizedData: any;
16
+ optimizedData: unknown;
10
17
  reductionRatio: number;
18
+ originalCharacters: number;
19
+ optimizedCharacters: number;
20
+ estimator: string;
11
21
  };
12
22
 
13
- export { analyze };
23
+ export { type AnalyzeOptions, analyze };
@@ -1,13 +1,23 @@
1
1
  /**
2
2
  * Safe version of analyze() - Prevents NaN values
3
3
  */
4
- declare function analyze(input: any, options?: any): {
4
+ type AnalyzeOptions = {
5
+ prune?: string[];
6
+ compact?: boolean;
7
+ flatten?: boolean;
8
+ toTOON?: boolean;
9
+ toon?: boolean;
10
+ };
11
+ declare function analyze(input: unknown, options?: AnalyzeOptions): {
5
12
  originalTokens: number;
6
13
  optimizedTokens: number;
7
14
  savings: number;
8
15
  savingsPercent: number;
9
- optimizedData: any;
16
+ optimizedData: unknown;
10
17
  reductionRatio: number;
18
+ originalCharacters: number;
19
+ optimizedCharacters: number;
20
+ estimator: string;
11
21
  };
12
22
 
13
- export { analyze };
23
+ export { type AnalyzeOptions, analyze };
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  analyze
3
- } from "../chunk-GQ62V6ZK.js";
4
- import "../chunk-3ECVBELU.js";
5
- import "../chunk-EUFLW42L.js";
6
- import "../chunk-YMOSWQGE.js";
7
- import "../chunk-IAV75SZA.js";
8
- import "../chunk-TM6L7YF2.js";
3
+ } from "../chunk-ZVQRYJU5.js";
4
+ import "../chunk-6VRLDRJK.js";
5
+ import "../chunk-XE36GLJP.js";
6
+ import "../chunk-L7BC62MT.js";
7
+ import "../chunk-ZD536GZF.js";
8
+ import "../chunk-7CL7GVRF.js";
9
9
  export {
10
10
  analyze
11
11
  };
@@ -45,7 +45,7 @@ function prune(obj, options = {}) {
45
45
  return arr;
46
46
  }
47
47
  const result = {};
48
- for (const key in obj) {
48
+ for (const key of Object.keys(obj)) {
49
49
  if (removeKeys.includes(key)) continue;
50
50
  const value = prune(obj[key], normalizedOptions);
51
51
  if (value === void 0) continue;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  compact
3
- } from "../chunk-EUFLW42L.js";
4
- import "../chunk-IAV75SZA.js";
3
+ } from "../chunk-XE36GLJP.js";
4
+ import "../chunk-ZD536GZF.js";
5
5
  export {
6
6
  compact
7
7
  };
@@ -26,20 +26,28 @@ module.exports = __toCommonJS(flatten_exports);
26
26
  function flatten(obj, prefix = "", res = {}) {
27
27
  if (obj === null || obj === void 0) return res;
28
28
  if (typeof obj !== "object") {
29
- res[prefix] = obj;
29
+ setValue(res, prefix, obj);
30
30
  return res;
31
31
  }
32
- for (const key in obj) {
32
+ for (const key of Object.keys(obj)) {
33
33
  const value = obj[key];
34
34
  const newKey = prefix ? `${prefix}.${key}` : key;
35
35
  if (typeof value === "object" && value !== null) {
36
36
  flatten(value, newKey, res);
37
37
  } else {
38
- res[newKey] = value;
38
+ setValue(res, newKey, value);
39
39
  }
40
40
  }
41
41
  return res;
42
42
  }
43
+ function setValue(res, key, value) {
44
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
45
+ throw new Error(
46
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
47
+ );
48
+ }
49
+ res[key] = value;
50
+ }
43
51
  // Annotate the CommonJS export names for ESM import in node:
44
52
  0 && (module.exports = {
45
53
  flatten
@@ -13,6 +13,6 @@
13
13
  * flatten({ user: { name: "Ali" } })
14
14
  * -> { "user.name": "Ali" }
15
15
  */
16
- declare function flatten(obj: any, prefix?: string, res?: any): any;
16
+ declare function flatten(obj: any, prefix?: string, res?: Record<string, unknown>): Record<string, unknown>;
17
17
 
18
18
  export { flatten };
@@ -13,6 +13,6 @@
13
13
  * flatten({ user: { name: "Ali" } })
14
14
  * -> { "user.name": "Ali" }
15
15
  */
16
- declare function flatten(obj: any, prefix?: string, res?: any): any;
16
+ declare function flatten(obj: any, prefix?: string, res?: Record<string, unknown>): Record<string, unknown>;
17
17
 
18
18
  export { flatten };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  flatten
3
- } from "../chunk-YMOSWQGE.js";
3
+ } from "../chunk-L7BC62MT.js";
4
4
  export {
5
5
  flatten
6
6
  };
@@ -43,7 +43,7 @@ function prune(obj, options = {}) {
43
43
  return arr;
44
44
  }
45
45
  const result = {};
46
- for (const key in obj) {
46
+ for (const key of Object.keys(obj)) {
47
47
  if (removeKeys.includes(key)) continue;
48
48
  const value = prune(obj[key], normalizedOptions);
49
49
  if (value === void 0) continue;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  prune
3
- } from "../chunk-IAV75SZA.js";
3
+ } from "../chunk-ZD536GZF.js";
4
4
  export {
5
5
  prune
6
6
  };
@@ -20,14 +20,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/core/token.ts
21
21
  var token_exports = {};
22
22
  __export(token_exports, {
23
- estimateTokens: () => estimateTokens
23
+ estimateTokens: () => estimateTokens,
24
+ serializeForTokenEstimate: () => serializeForTokenEstimate
24
25
  });
25
26
  module.exports = __toCommonJS(token_exports);
26
- function estimateTokens(text) {
27
- if (!text) return 0;
28
- return Math.ceil(text.length / 4);
27
+ function serializeForTokenEstimate(value) {
28
+ if (typeof value === "string") return value;
29
+ const serialized = JSON.stringify(value);
30
+ return serialized ?? "";
31
+ }
32
+ function estimateTokens(value) {
33
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
29
34
  }
30
35
  // Annotate the CommonJS export names for ESM import in node:
31
36
  0 && (module.exports = {
32
- estimateTokens
37
+ estimateTokens,
38
+ serializeForTokenEstimate
33
39
  });
@@ -7,6 +7,12 @@
7
7
  * @param text - Input string
8
8
  * @returns Estimated token count
9
9
  */
10
- declare function estimateTokens(text: string): number;
10
+ declare function serializeForTokenEstimate(value: unknown): string;
11
+ /**
12
+ * Estimates tokens using a transparent four-characters-per-token heuristic.
13
+ * JSON values are serialized first, matching the compact form normally sent
14
+ * to an API. Use a model-specific tokenizer for billing-accurate counts.
15
+ */
16
+ declare function estimateTokens(value: unknown): number;
11
17
 
12
- export { estimateTokens };
18
+ export { estimateTokens, serializeForTokenEstimate };
@@ -7,6 +7,12 @@
7
7
  * @param text - Input string
8
8
  * @returns Estimated token count
9
9
  */
10
- declare function estimateTokens(text: string): number;
10
+ declare function serializeForTokenEstimate(value: unknown): string;
11
+ /**
12
+ * Estimates tokens using a transparent four-characters-per-token heuristic.
13
+ * JSON values are serialized first, matching the compact form normally sent
14
+ * to an API. Use a model-specific tokenizer for billing-accurate counts.
15
+ */
16
+ declare function estimateTokens(value: unknown): number;
11
17
 
12
- export { estimateTokens };
18
+ export { estimateTokens, serializeForTokenEstimate };
@@ -1,6 +1,8 @@
1
1
  import {
2
- estimateTokens
3
- } from "../chunk-TM6L7YF2.js";
2
+ estimateTokens,
3
+ serializeForTokenEstimate
4
+ } from "../chunk-7CL7GVRF.js";
4
5
  export {
5
- estimateTokens
6
+ estimateTokens,
7
+ serializeForTokenEstimate
6
8
  };
@@ -38,11 +38,11 @@ function toTOON(data, indent = 0) {
38
38
  }
39
39
  return result;
40
40
  }
41
- return `${space}[${data.length}]: ${data.join(",")}`;
41
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
42
42
  }
43
43
  if (typeof data === "object" && data !== null) {
44
44
  let result = "";
45
- for (const key in data) {
45
+ for (const key of Object.keys(data)) {
46
46
  const value = data[key];
47
47
  if (typeof value === "object" && value !== null) {
48
48
  result += `${space}${key}:
@@ -61,7 +61,9 @@ ${toTOON(value, indent + 1)}
61
61
  }
62
62
  function formatValue(val) {
63
63
  if (val === null || val === void 0) return "";
64
- if (typeof val === "string") return val;
64
+ if (typeof val === "string") {
65
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
66
+ }
65
67
  return String(val);
66
68
  }
67
69
  function isPlainObject(value) {
package/dist/core/toon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  toTOON
3
- } from "../chunk-3ECVBELU.js";
3
+ } from "../chunk-6VRLDRJK.js";
4
4
  export {
5
5
  toTOON
6
6
  };
package/dist/index.cjs CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  estimateTokens: () => estimateTokens,
28
28
  flatten: () => flatten,
29
29
  prune: () => prune,
30
+ serializeForTokenEstimate: () => serializeForTokenEstimate,
30
31
  toNatural: () => toNatural,
31
32
  toTOON: () => toTOON
32
33
  });
@@ -53,7 +54,7 @@ function prune(obj, options = {}) {
53
54
  return arr;
54
55
  }
55
56
  const result = {};
56
- for (const key in obj) {
57
+ for (const key of Object.keys(obj)) {
57
58
  if (removeKeys.includes(key)) continue;
58
59
  const value = prune(obj[key], normalizedOptions);
59
60
  if (value === void 0) continue;
@@ -69,20 +70,28 @@ function prune(obj, options = {}) {
69
70
  function flatten(obj, prefix = "", res = {}) {
70
71
  if (obj === null || obj === void 0) return res;
71
72
  if (typeof obj !== "object") {
72
- res[prefix] = obj;
73
+ setValue(res, prefix, obj);
73
74
  return res;
74
75
  }
75
- for (const key in obj) {
76
+ for (const key of Object.keys(obj)) {
76
77
  const value = obj[key];
77
78
  const newKey = prefix ? `${prefix}.${key}` : key;
78
79
  if (typeof value === "object" && value !== null) {
79
80
  flatten(value, newKey, res);
80
81
  } else {
81
- res[newKey] = value;
82
+ setValue(res, newKey, value);
82
83
  }
83
84
  }
84
85
  return res;
85
86
  }
87
+ function setValue(res, key, value) {
88
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
89
+ throw new Error(
90
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
91
+ );
92
+ }
93
+ res[key] = value;
94
+ }
86
95
 
87
96
  // src/core/compact.ts
88
97
  function compact(obj) {
@@ -106,11 +115,11 @@ function toTOON(data, indent = 0) {
106
115
  }
107
116
  return result;
108
117
  }
109
- return `${space}[${data.length}]: ${data.join(",")}`;
118
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
110
119
  }
111
120
  if (typeof data === "object" && data !== null) {
112
121
  let result = "";
113
- for (const key in data) {
122
+ for (const key of Object.keys(data)) {
114
123
  const value = data[key];
115
124
  if (typeof value === "object" && value !== null) {
116
125
  result += `${space}${key}:
@@ -129,7 +138,9 @@ ${toTOON(value, indent + 1)}
129
138
  }
130
139
  function formatValue(val) {
131
140
  if (val === null || val === void 0) return "";
132
- if (typeof val === "string") return val;
141
+ if (typeof val === "string") {
142
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
143
+ }
133
144
  return String(val);
134
145
  }
135
146
  function isPlainObject(value) {
@@ -137,9 +148,13 @@ function isPlainObject(value) {
137
148
  }
138
149
 
139
150
  // src/core/token.ts
140
- function estimateTokens(text) {
141
- if (!text) return 0;
142
- return Math.ceil(text.length / 4);
151
+ function serializeForTokenEstimate(value) {
152
+ if (typeof value === "string") return value;
153
+ const serialized = JSON.stringify(value);
154
+ return serialized ?? "";
155
+ }
156
+ function estimateTokens(value) {
157
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
143
158
  }
144
159
 
145
160
  // src/core/analyze.ts
@@ -151,11 +166,13 @@ function analyze(input, options = {}) {
151
166
  savings: 0,
152
167
  savingsPercent: 0,
153
168
  optimizedData: null,
154
- reductionRatio: 1
169
+ reductionRatio: 1,
170
+ originalCharacters: 0,
171
+ optimizedCharacters: 0,
172
+ estimator: "heuristic: 1 token \u2248 4 characters"
155
173
  };
156
174
  }
157
- let originalTokens = estimateTokens(input);
158
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
175
+ const originalTokens = estimateTokens(input);
159
176
  let optimizedData = input;
160
177
  if (options.prune && Array.isArray(options.prune)) {
161
178
  optimizedData = prune(optimizedData, options.prune);
@@ -169,8 +186,7 @@ function analyze(input, options = {}) {
169
186
  if (options.toTOON === true || options.toon === true) {
170
187
  optimizedData = toTOON(optimizedData);
171
188
  }
172
- let optimizedTokens = estimateTokens(optimizedData);
173
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
189
+ const optimizedTokens = estimateTokens(optimizedData);
174
190
  const savings = Math.max(0, originalTokens - optimizedTokens);
175
191
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
176
192
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
@@ -180,7 +196,10 @@ function analyze(input, options = {}) {
180
196
  savings,
181
197
  savingsPercent,
182
198
  optimizedData,
183
- reductionRatio
199
+ reductionRatio,
200
+ originalCharacters: serializeForTokenEstimate(input).length,
201
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
202
+ estimator: "heuristic: 1 token \u2248 4 characters"
184
203
  };
185
204
  }
186
205
 
@@ -329,6 +348,7 @@ ai.toTOON = toTOON;
329
348
  ai.analyze = analyze;
330
349
  ai.toNatural = toNatural;
331
350
  ai.estimateTokens = estimateTokens;
351
+ ai.serializeForTokenEstimate = serializeForTokenEstimate;
332
352
  ai.AIChain = AIChain;
333
353
  var index_default = ai;
334
354
  // Annotate the CommonJS export names for ESM import in node:
@@ -339,6 +359,7 @@ var index_default = ai;
339
359
  estimateTokens,
340
360
  flatten,
341
361
  prune,
362
+ serializeForTokenEstimate,
342
363
  toNatural,
343
364
  toTOON
344
365
  });
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { estimateTokens } from './core/token.cjs';
2
- import { toNatural } from './core/natural.cjs';
3
- import { analyze } from './core/analyze.cjs';
4
- import { toTOON } from './core/toon.cjs';
5
- import { flatten } from './core/flatten.cjs';
6
- import { compact } from './core/compact.cjs';
7
1
  import { prune } from './core/prune.cjs';
2
+ import { compact } from './core/compact.cjs';
3
+ import { flatten } from './core/flatten.cjs';
4
+ import { toTOON } from './core/toon.cjs';
5
+ import { analyze } from './core/analyze.cjs';
6
+ import { toNatural } from './core/natural.cjs';
7
+ import { estimateTokens, serializeForTokenEstimate } from './core/token.cjs';
8
8
 
9
9
  declare class AIChain {
10
10
  private data;
@@ -19,8 +19,11 @@ declare class AIChain {
19
19
  optimizedTokens: number;
20
20
  savings: number;
21
21
  savingsPercent: number;
22
- optimizedData: any;
22
+ optimizedData: unknown;
23
23
  reductionRatio: number;
24
+ originalCharacters: number;
25
+ optimizedCharacters: number;
26
+ estimator: string;
24
27
  };
25
28
  value(): any;
26
29
  }
@@ -34,7 +37,8 @@ declare namespace ai {
34
37
  var analyze: typeof analyze;
35
38
  var toNatural: typeof toNatural;
36
39
  var estimateTokens: typeof estimateTokens;
40
+ var serializeForTokenEstimate: typeof serializeForTokenEstimate;
37
41
  var AIChain: typeof AIChain;
38
42
  }
39
43
 
40
- export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, toNatural, toTOON };
44
+ export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, serializeForTokenEstimate, toNatural, toTOON };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { estimateTokens } from './core/token.js';
2
- import { toNatural } from './core/natural.js';
3
- import { analyze } from './core/analyze.js';
4
- import { toTOON } from './core/toon.js';
5
- import { flatten } from './core/flatten.js';
6
- import { compact } from './core/compact.js';
7
1
  import { prune } from './core/prune.js';
2
+ import { compact } from './core/compact.js';
3
+ import { flatten } from './core/flatten.js';
4
+ import { toTOON } from './core/toon.js';
5
+ import { analyze } from './core/analyze.js';
6
+ import { toNatural } from './core/natural.js';
7
+ import { estimateTokens, serializeForTokenEstimate } from './core/token.js';
8
8
 
9
9
  declare class AIChain {
10
10
  private data;
@@ -19,8 +19,11 @@ declare class AIChain {
19
19
  optimizedTokens: number;
20
20
  savings: number;
21
21
  savingsPercent: number;
22
- optimizedData: any;
22
+ optimizedData: unknown;
23
23
  reductionRatio: number;
24
+ originalCharacters: number;
25
+ optimizedCharacters: number;
26
+ estimator: string;
24
27
  };
25
28
  value(): any;
26
29
  }
@@ -34,7 +37,8 @@ declare namespace ai {
34
37
  var analyze: typeof analyze;
35
38
  var toNatural: typeof toNatural;
36
39
  var estimateTokens: typeof estimateTokens;
40
+ var serializeForTokenEstimate: typeof serializeForTokenEstimate;
37
41
  var AIChain: typeof AIChain;
38
42
  }
39
43
 
40
- export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, toNatural, toTOON };
44
+ export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, serializeForTokenEstimate, toNatural, toTOON };
package/dist/index.js CHANGED
@@ -1,27 +1,28 @@
1
1
  import {
2
2
  AIChain
3
- } from "./chunk-2IQAGI7Q.js";
3
+ } from "./chunk-HFMW3K22.js";
4
4
  import {
5
5
  analyze
6
- } from "./chunk-GQ62V6ZK.js";
6
+ } from "./chunk-ZVQRYJU5.js";
7
7
  import {
8
8
  toTOON
9
- } from "./chunk-3ECVBELU.js";
9
+ } from "./chunk-6VRLDRJK.js";
10
10
  import {
11
11
  compact
12
- } from "./chunk-EUFLW42L.js";
12
+ } from "./chunk-XE36GLJP.js";
13
13
  import {
14
14
  flatten
15
- } from "./chunk-YMOSWQGE.js";
15
+ } from "./chunk-L7BC62MT.js";
16
16
  import {
17
17
  toNatural
18
18
  } from "./chunk-D4CFTFM3.js";
19
19
  import {
20
20
  prune
21
- } from "./chunk-IAV75SZA.js";
21
+ } from "./chunk-ZD536GZF.js";
22
22
  import {
23
- estimateTokens
24
- } from "./chunk-TM6L7YF2.js";
23
+ estimateTokens,
24
+ serializeForTokenEstimate
25
+ } from "./chunk-7CL7GVRF.js";
25
26
 
26
27
  // src/index.ts
27
28
  function ai(data) {
@@ -34,6 +35,7 @@ ai.toTOON = toTOON;
34
35
  ai.analyze = analyze;
35
36
  ai.toNatural = toNatural;
36
37
  ai.estimateTokens = estimateTokens;
38
+ ai.serializeForTokenEstimate = serializeForTokenEstimate;
37
39
  ai.AIChain = AIChain;
38
40
  var index_default = ai;
39
41
  export {
@@ -44,6 +46,7 @@ export {
44
46
  estimateTokens,
45
47
  flatten,
46
48
  prune,
49
+ serializeForTokenEstimate,
47
50
  toNatural,
48
51
  toTOON
49
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sohqureshi/tokenwise",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Optimize JSON data for AI by reducing token usage",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,6 +60,8 @@
60
60
  "scripts": {
61
61
  "build": "tsup src/index.ts src/cli.ts src/core/analyze.ts src/core/compact.ts src/core/flatten.ts src/core/natural.ts src/core/prune.ts src/core/token.ts src/core/toon.ts --format esm,cjs --dts",
62
62
  "test": "vitest",
63
+ "test:run": "vitest run",
64
+ "typecheck": "tsc --noEmit",
63
65
  "prepare": "npm run build"
64
66
  },
65
67
  "keywords": [
@@ -1,9 +0,0 @@
1
- // src/core/token.ts
2
- function estimateTokens(text) {
3
- if (!text) return 0;
4
- return Math.ceil(text.length / 4);
5
- }
6
-
7
- export {
8
- estimateTokens
9
- };