brevit 0.1.1

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/src/brevit.js ADDED
@@ -0,0 +1,589 @@
1
+ /*
2
+ * =================================================================================
3
+ * BREVIT.JS (brevit-js)
4
+ *
5
+ * A high-performance JavaScript library for semantically compressing
6
+ * and optimizing data before sending it to a Large Language Model (LLM).
7
+ *
8
+ * Project: Brevit
9
+ * Author: Javian
10
+ * Version: 0.1.0
11
+ * =================================================================================
12
+ */
13
+
14
+ // Define enums for configuration (using a JS object)
15
+ export const JsonOptimizationMode = {
16
+ None: 'None',
17
+ Flatten: 'Flatten',
18
+ ToYaml: 'ToYaml', // Note: Requires a YAML library like 'js-yaml'
19
+ Filter: 'Filter', // Note: Requires a JSON-path library or custom logic
20
+ };
21
+
22
+ export const TextOptimizationMode = {
23
+ None: 'None',
24
+ Clean: 'Clean',
25
+ SummarizeFast: 'SummarizeFast',
26
+ SummarizeHighQuality: 'SummarizeHighQuality',
27
+ };
28
+
29
+ export const ImageOptimizationMode = {
30
+ None: 'None',
31
+ Ocr: 'Ocr',
32
+ Metadata: 'Metadata',
33
+ };
34
+
35
+ /**
36
+ * Configuration object for the BrevitClient.
37
+ */
38
+ export class BrevitConfig {
39
+ /**
40
+ * @param {object} options
41
+ * @param {string} options.jsonMode - Strategy for JSON optimization.
42
+ * @param {string} options.textMode - Strategy for Text optimization.
43
+ * @param {string} options.imageMode - Strategy for Image optimization.
44
+ * @param {string[]} options.jsonPathsToKeep - Paths to keep for Filter mode.
45
+ * @param {number} options.longTextThreshold - Char count to trigger text optimization.
46
+ */
47
+ constructor({
48
+ jsonMode = JsonOptimizationMode.Flatten,
49
+ textMode = TextOptimizationMode.Clean,
50
+ imageMode = ImageOptimizationMode.Ocr,
51
+ jsonPathsToKeep = [],
52
+ longTextThreshold = 500,
53
+ } = {}) {
54
+ this.jsonMode = jsonMode;
55
+ this.textMode = textMode;
56
+ this.imageMode = imageMode;
57
+ this.jsonPathsToKeep = jsonPathsToKeep;
58
+ this.longTextThreshold = longTextThreshold;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * The main client for the Brevit.js library.
64
+ * This class orchestrates the optimization pipeline.
65
+ */
66
+ export class BrevitClient {
67
+ /**
68
+ * @param {BrevitConfig} config
69
+ * @param {Object} options - Optional custom optimizers
70
+ * @param {Function} options.textOptimizer - Custom text optimizer function
71
+ * @param {Function} options.imageOptimizer - Custom image optimizer function
72
+ */
73
+ constructor(config = new BrevitConfig(), options = {}) {
74
+ this._config = config;
75
+ this._textOptimizer = options.textOptimizer || this._defaultTextOptimizer.bind(this);
76
+ this._imageOptimizer = options.imageOptimizer || this._defaultImageOptimizer.bind(this);
77
+ }
78
+
79
+ /**
80
+ * Checks if an array contains uniform objects (all have same keys).
81
+ * @param {Array} arr - The array to check
82
+ * @returns {Object|null} Object with keys array if uniform, null otherwise
83
+ * @private
84
+ */
85
+ _isUniformObjectArray(arr) {
86
+ if (!Array.isArray(arr) || arr.length === 0) return null;
87
+
88
+ const firstItem = arr[0];
89
+ if (typeof firstItem !== 'object' || firstItem === null || Array.isArray(firstItem)) {
90
+ return null;
91
+ }
92
+
93
+ // Preserve original field order instead of sorting
94
+ const firstKeys = Object.keys(firstItem);
95
+ const firstKeySet = new Set(firstKeys);
96
+
97
+ // Check if all items have the same keys (order-independent)
98
+ for (let i = 1; i < arr.length; i++) {
99
+ const item = arr[i];
100
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
101
+ return null;
102
+ }
103
+ const itemKeys = Object.keys(item);
104
+ if (firstKeys.length !== itemKeys.length) {
105
+ return null;
106
+ }
107
+ // Check if all keys exist (order doesn't matter for uniformity)
108
+ if (!itemKeys.every(key => firstKeySet.has(key))) {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ return { keys: firstKeys };
114
+ }
115
+
116
+ /**
117
+ * Checks if an array contains only primitives of compatible types.
118
+ * @param {Array} arr - The array to check
119
+ * @returns {boolean} True if all elements are primitives
120
+ * @private
121
+ */
122
+ _isPrimitiveArray(arr) {
123
+ if (!Array.isArray(arr) || arr.length === 0) return false;
124
+
125
+ const firstType = typeof arr[0];
126
+ if (firstType === 'object' && arr[0] !== null) return false;
127
+
128
+ // Check if all elements are primitives
129
+ for (let i = 1; i < arr.length; i++) {
130
+ const itemType = typeof arr[i];
131
+ if (itemType === 'object' && arr[i] !== null) return false;
132
+ }
133
+
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * Escapes a value for comma-separated format.
139
+ * @param {any} value - The value to escape
140
+ * @returns {string} Escaped string
141
+ * @private
142
+ */
143
+ _escapeValue(value) {
144
+ const str = String(value);
145
+ // Quote if contains comma, newline, or quotes
146
+ if (str.includes(',') || str.includes('\n') || str.includes('"')) {
147
+ return `"${str.replace(/"/g, '\\"')}"`;
148
+ }
149
+ return str;
150
+ }
151
+
152
+ /**
153
+ * Formats a uniform object array in tabular format.
154
+ * @param {Array} arr - The uniform object array
155
+ * @param {string} prefix - The key path prefix
156
+ * @returns {string} Formatted tabular string
157
+ * @private
158
+ */
159
+ _formatTabularArray(arr, prefix) {
160
+ const { keys } = this._isUniformObjectArray(arr);
161
+ const header = `${prefix}[${arr.length}]{${keys.join(',')}}:`;
162
+ const rows = arr.map(item =>
163
+ keys.map(key => this._escapeValue(item[key] ?? 'null')).join(',')
164
+ );
165
+ return `${header}\n${rows.join('\n')}`;
166
+ }
167
+
168
+ /**
169
+ * Formats a primitive array in comma-separated format.
170
+ * @param {Array} arr - The primitive array
171
+ * @param {string} prefix - The key path prefix
172
+ * @returns {string} Formatted comma-separated string
173
+ * @private
174
+ */
175
+ _formatPrimitiveArray(arr, prefix) {
176
+ const values = arr.map(item => this._escapeValue(item));
177
+ return `${prefix}[${arr.length}]:${values.join(',')}`;
178
+ }
179
+
180
+ /**
181
+ * Recursive helper for flattening a JSON object/array with tabular optimization.
182
+ * @param {any} node - The current JS object, array, or value.
183
+ * @param {string} prefix - The key path built so far.
184
+ * @param {Array<string>} output - The output array of formatted lines.
185
+ * @private
186
+ */
187
+ _flatten(node, prefix = '', output = []) {
188
+ if (typeof node === 'object' && node !== null && !Array.isArray(node)) {
189
+ // It's an object
190
+ Object.entries(node).forEach(([key, value]) => {
191
+ const newPrefix = prefix ? `${prefix}.${key}` : key;
192
+ this._flatten(value, newPrefix, output);
193
+ });
194
+ } else if (Array.isArray(node)) {
195
+ // It's an array - check for optimization opportunities
196
+
197
+ // Check for uniform object array (tabular format)
198
+ const uniformCheck = this._isUniformObjectArray(node);
199
+ if (uniformCheck) {
200
+ output.push(this._formatTabularArray(node, prefix));
201
+ return;
202
+ }
203
+
204
+ // Check for primitive array (comma-separated format)
205
+ if (this._isPrimitiveArray(node)) {
206
+ output.push(this._formatPrimitiveArray(node, prefix));
207
+ return;
208
+ }
209
+
210
+ // Fall back to current format for mixed/non-uniform arrays
211
+ node.forEach((item, index) => {
212
+ const newPrefix = `${prefix}[${index}]`;
213
+ this._flatten(item, newPrefix, output);
214
+ });
215
+ } else {
216
+ // It's a primitive value (string, number, boolean, null)
217
+ if (!prefix) prefix = 'value'; // Handle root-level value
218
+ output.push(`${prefix}:${String(node)}`);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Flattens a JS object into a token-efficient string with tabular optimization.
224
+ * @param {object} obj - The object to flatten.
225
+ * @returns {string} The flattened string.
226
+ * @private
227
+ */
228
+ _flattenObject(obj) {
229
+ const output = [];
230
+ this._flatten(obj, '', output);
231
+ return output.join('\n');
232
+ }
233
+
234
+ /**
235
+ * Analyzes data structure to determine the best optimization strategy.
236
+ * @param {any} data - The data to analyze
237
+ * @returns {Object} Analysis result with recommended strategy
238
+ * @private
239
+ */
240
+ _analyzeDataStructure(data) {
241
+ const analysis = {
242
+ type: null,
243
+ depth: 0,
244
+ hasUniformArrays: false,
245
+ hasPrimitiveArrays: false,
246
+ hasNestedObjects: false,
247
+ textLength: 0,
248
+ arrayCount: 0,
249
+ objectCount: 0,
250
+ complexity: 'simple'
251
+ };
252
+
253
+ const analyze = (node, depth = 0, path = '') => {
254
+ analysis.depth = Math.max(analysis.depth, depth);
255
+
256
+ if (typeof node === 'string') {
257
+ analysis.textLength += node.length;
258
+ return;
259
+ }
260
+
261
+ if (Array.isArray(node)) {
262
+ analysis.arrayCount++;
263
+
264
+ // Check for uniform object arrays
265
+ const uniformCheck = this._isUniformObjectArray(node);
266
+ if (uniformCheck) {
267
+ analysis.hasUniformArrays = true;
268
+ }
269
+
270
+ // Check for primitive arrays
271
+ if (this._isPrimitiveArray(node)) {
272
+ analysis.hasPrimitiveArrays = true;
273
+ }
274
+
275
+ // Analyze each element
276
+ node.forEach((item, index) => {
277
+ analyze(item, depth + 1, `${path}[${index}]`);
278
+ });
279
+ } else if (typeof node === 'object' && node !== null) {
280
+ analysis.objectCount++;
281
+
282
+ if (depth > 0) {
283
+ analysis.hasNestedObjects = true;
284
+ }
285
+
286
+ Object.entries(node).forEach(([key, value]) => {
287
+ analyze(value, depth + 1, path ? `${path}.${key}` : key);
288
+ });
289
+ }
290
+ };
291
+
292
+ analyze(data);
293
+
294
+ // Determine complexity
295
+ if (analysis.depth > 3 || analysis.arrayCount > 5 || analysis.objectCount > 10) {
296
+ analysis.complexity = 'complex';
297
+ } else if (analysis.depth > 1 || analysis.arrayCount > 0 || analysis.objectCount > 3) {
298
+ analysis.complexity = 'moderate';
299
+ }
300
+
301
+ // Determine type
302
+ if (typeof data === 'string') {
303
+ analysis.type = data.length > this._config.longTextThreshold ? 'longText' : 'text';
304
+ } else if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
305
+ analysis.type = 'image';
306
+ } else if (Array.isArray(data)) {
307
+ analysis.type = 'array';
308
+ } else if (typeof data === 'object' && data !== null) {
309
+ analysis.type = 'object';
310
+ } else {
311
+ analysis.type = 'primitive';
312
+ }
313
+
314
+ return analysis;
315
+ }
316
+
317
+ /**
318
+ * Selects the best optimization strategy based on data analysis.
319
+ * @param {Object} analysis - Data structure analysis
320
+ * @returns {Object} Strategy configuration
321
+ * @private
322
+ */
323
+ _selectOptimalStrategy(analysis) {
324
+ // Strategy scoring: higher score = better fit
325
+ const strategies = [];
326
+
327
+ // Strategy 1: Flatten with tabular optimization (best for uniform arrays)
328
+ if (analysis.hasUniformArrays || analysis.hasPrimitiveArrays) {
329
+ strategies.push({
330
+ name: 'Flatten',
331
+ jsonMode: JsonOptimizationMode.Flatten,
332
+ score: analysis.hasUniformArrays ? 100 : 80,
333
+ reason: analysis.hasUniformArrays
334
+ ? 'Uniform object arrays detected - tabular format optimal'
335
+ : 'Primitive arrays detected - comma-separated format optimal'
336
+ });
337
+ }
338
+
339
+ // Strategy 2: Standard flatten (good for nested objects)
340
+ if (analysis.hasNestedObjects || analysis.complexity === 'moderate') {
341
+ strategies.push({
342
+ name: 'Flatten',
343
+ jsonMode: JsonOptimizationMode.Flatten,
344
+ score: 70,
345
+ reason: 'Nested objects detected - flatten format optimal'
346
+ });
347
+ }
348
+
349
+ // Strategy 3: YAML (good for readable structures)
350
+ if (analysis.complexity === 'moderate' && !analysis.hasUniformArrays) {
351
+ strategies.push({
352
+ name: 'ToYaml',
353
+ jsonMode: JsonOptimizationMode.ToYaml,
354
+ score: 60,
355
+ reason: 'Moderate complexity - YAML format may be more readable'
356
+ });
357
+ }
358
+
359
+ // Strategy 4: Text optimization (for long text)
360
+ if (analysis.type === 'longText') {
361
+ strategies.push({
362
+ name: 'TextOptimization',
363
+ textMode: this._config.textMode,
364
+ score: 90,
365
+ reason: 'Long text detected - summarization recommended'
366
+ });
367
+ }
368
+
369
+ // Strategy 5: Image optimization (for image data)
370
+ if (analysis.type === 'image') {
371
+ strategies.push({
372
+ name: 'ImageOptimization',
373
+ imageMode: this._config.imageMode,
374
+ score: 100,
375
+ reason: 'Image data detected - OCR recommended'
376
+ });
377
+ }
378
+
379
+ // Select highest scoring strategy
380
+ if (strategies.length === 0) {
381
+ return {
382
+ name: 'Flatten',
383
+ jsonMode: JsonOptimizationMode.Flatten,
384
+ score: 50,
385
+ reason: 'Default flatten strategy'
386
+ };
387
+ }
388
+
389
+ return strategies.reduce((best, current) =>
390
+ current.score > best.score ? current : best
391
+ );
392
+ }
393
+
394
+ /**
395
+ * Intelligently optimizes data by automatically selecting the best strategy.
396
+ * This method analyzes the input data structure and applies the most
397
+ * appropriate optimization methods automatically.
398
+ *
399
+ * @param {any} rawData - The data to optimize (object, JSON string, text, ArrayBuffer).
400
+ * @param {string} [intent] - (Optional) A hint about the user's goal.
401
+ * @returns {Promise<string>} A promise that resolves to the optimized string.
402
+ */
403
+ async brevity(rawData, intent = null) {
404
+ // Normalize input to object for analysis
405
+ let inputObject = null;
406
+ let inputType = typeof rawData;
407
+
408
+ if (inputType === 'string') {
409
+ try {
410
+ const trimmed = rawData.trim();
411
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
412
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
413
+ inputObject = JSON.parse(rawData);
414
+ } else {
415
+ // It's plain text - analyze and optimize
416
+ const analysis = this._analyzeDataStructure(rawData);
417
+ const strategy = this._selectOptimalStrategy(analysis);
418
+
419
+ if (strategy.name === 'TextOptimization') {
420
+ return await this._textOptimizer(rawData, intent);
421
+ }
422
+ return rawData;
423
+ }
424
+ } catch (e) {
425
+ // Not JSON - treat as text
426
+ const analysis = this._analyzeDataStructure(rawData);
427
+ const strategy = this._selectOptimalStrategy(analysis);
428
+
429
+ if (strategy.name === 'TextOptimization') {
430
+ return await this._textOptimizer(rawData, intent);
431
+ }
432
+ return rawData;
433
+ }
434
+ } else if (inputType === 'object' && rawData !== null) {
435
+ // Check if it's image data
436
+ if (rawData instanceof ArrayBuffer ||
437
+ rawData instanceof Uint8Array ||
438
+ (rawData.constructor && rawData.constructor.name === 'Buffer')) {
439
+ return await this._imageOptimizer(rawData, intent);
440
+ }
441
+ inputObject = rawData;
442
+ } else {
443
+ // Primitive - return as-is
444
+ return String(rawData);
445
+ }
446
+
447
+ // Analyze the data structure
448
+ const analysis = this._analyzeDataStructure(inputObject);
449
+ const strategy = this._selectOptimalStrategy(analysis);
450
+
451
+ // Apply the selected strategy
452
+ const tempConfig = new BrevitConfig({
453
+ ...this._config,
454
+ jsonMode: strategy.jsonMode || this._config.jsonMode,
455
+ textMode: strategy.textMode || this._config.textMode,
456
+ imageMode: strategy.imageMode || this._config.imageMode
457
+ });
458
+
459
+ // Temporarily override config for this optimization
460
+ const originalConfig = this._config;
461
+ this._config = tempConfig;
462
+
463
+ try {
464
+ // Use the existing optimize method with the selected strategy
465
+ return await this.optimize(inputObject, intent);
466
+ } finally {
467
+ // Restore original config
468
+ this._config = originalConfig;
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Registers a custom optimization strategy for the brevity method.
474
+ * This allows extending Brevit with new optimization strategies.
475
+ *
476
+ * @param {string} name - Strategy name
477
+ * @param {Function} analyzer - Function that analyzes data and returns score (0-100)
478
+ * @param {Function} optimizer - Function that optimizes the data
479
+ * @example
480
+ * brevit.registerStrategy('custom', (data) => ({ score: 85, reason: 'Custom logic' }), async (data) => { ... });
481
+ */
482
+ registerStrategy(name, analyzer, optimizer) {
483
+ if (!this._strategies) {
484
+ this._strategies = new Map();
485
+ }
486
+ this._strategies.set(name, { analyzer, optimizer });
487
+ }
488
+
489
+ /**
490
+ * The primary method. Optimizes any JS object, JSON string,
491
+ * or text into a token-efficient string.
492
+ *
493
+ * @param {any} rawData - The data to optimize (object, JSON string, text, ArrayBuffer).
494
+ * @param {string} [intent] - (Optional) A hint about the user's goal.
495
+ * @returns {Promise<string>} A promise that resolves to the optimized string.
496
+ */
497
+ async optimize(rawData, intent = null) {
498
+ let inputObject = null;
499
+ let inputType = typeof rawData;
500
+
501
+ if (inputType === 'string') {
502
+ // Could be JSON string or just text
503
+ try {
504
+ const trimmed = rawData.trim();
505
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
506
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
507
+ inputObject = JSON.parse(rawData);
508
+ }
509
+ } catch (e) {
510
+ // It's not a JSON string, treat as text
511
+ }
512
+
513
+ if (!inputObject) {
514
+ // It's text
515
+ if (rawData.length > this._config.longTextThreshold) {
516
+ // It's long text, apply text optimization
517
+ return await this._textOptimizer(rawData, intent);
518
+ }
519
+ // It's short text, return as-is
520
+ return rawData;
521
+ }
522
+ } else if (inputType === 'object' && rawData !== null) {
523
+ // Check if it's an ArrayBuffer or TypedArray (image data)
524
+ if (rawData instanceof ArrayBuffer ||
525
+ rawData instanceof Uint8Array ||
526
+ (rawData.constructor && rawData.constructor.name === 'Buffer')) {
527
+ return await this._imageOptimizer(rawData, intent);
528
+ }
529
+ // It's a plain JS object
530
+ inputObject = rawData;
531
+ } else {
532
+ // Other primitives, return as-is
533
+ return String(rawData);
534
+ }
535
+
536
+ // If we're here, we have an object (from JSON or POJO)
537
+ // Now apply the configured JSON optimization
538
+ switch (this._config.jsonMode) {
539
+ case JsonOptimizationMode.Flatten:
540
+ return this._flattenObject(inputObject);
541
+
542
+ case JsonOptimizationMode.ToYaml:
543
+ // STUB: Requires a 'js-yaml' library
544
+ // import YAML from 'js-yaml';
545
+ // return YAML.dump(inputObject);
546
+ console.warn('[Brevit] ToYaml mode requires installing a YAML library (e.g., js-yaml).');
547
+ return JSON.stringify(inputObject, null, 2); // Fallback
548
+
549
+ case JsonOptimizationMode.Filter:
550
+ // STUB: Requires a JSON-path library
551
+ console.warn('[Brevit] Filter mode is not implemented in this stub.');
552
+ return JSON.stringify(inputObject); // Fallback
553
+
554
+ case JsonOptimizationMode.None:
555
+ default:
556
+ return JSON.stringify(inputObject); // Return as unformatted JSON
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Default text optimizer (STUB).
562
+ * In a frontend, this would likely make an API call to a
563
+ * backend (C# or Python) that runs Semantic Kernel or LangChain
564
+ * to do the actual summarization.
565
+ * @private
566
+ */
567
+ async _defaultTextOptimizer(longText, intent) {
568
+ // STUB: A real frontend app would call its backend for this.
569
+ // NEVER put LLM API keys in a frontend app.
570
+ console.warn('[Brevit] Text summarization should be done on a secure backend.');
571
+ const mode = this._config.textMode;
572
+ const stubSummary = longText.substring(0, 150);
573
+ return `[${mode} Stub: Summary of text follows...]\n${stubSummary}...\n[End of summary]`;
574
+ }
575
+
576
+ /**
577
+ * Default image optimizer (STUB).
578
+ * In a frontend, this would likely make an API call to a
579
+ * backend that runs OCR services.
580
+ * @private
581
+ */
582
+ async _defaultImageOptimizer(imageData, intent) {
583
+ // STUB: A real frontend app would call its backend for this.
584
+ console.warn('[Brevit] Image OCR should be done on a secure backend.');
585
+ const size = imageData instanceof ArrayBuffer ? imageData.byteLength : imageData.length;
586
+ return `[OCR Stub: Extracted text from image (${size} bytes)]\nSample OCR Text: INVOICE #1234\nTotal: $499.99\n[End of extracted text]`;
587
+ }
588
+ }
589
+
package/test/test.js ADDED
@@ -0,0 +1,90 @@
1
+ import { BrevitClient, BrevitConfig, JsonOptimizationMode } from '../src/brevit.js';
2
+
3
+ async function runTests() {
4
+ console.log('Running Brevit.js Tests...\n');
5
+
6
+ let passed = 0;
7
+ let failed = 0;
8
+
9
+ function test(name, fn) {
10
+ try {
11
+ fn();
12
+ console.log(`✓ ${name}`);
13
+ passed++;
14
+ } catch (error) {
15
+ console.error(`✗ ${name}: ${error.message}`);
16
+ failed++;
17
+ }
18
+ }
19
+
20
+ // Test 1: Flatten JSON object
21
+ test('Flatten JSON object', async () => {
22
+ const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
23
+ const brevit = new BrevitClient(config);
24
+
25
+ const testObject = {
26
+ user: {
27
+ name: 'Javian',
28
+ email: 'support@javianpicardo.com'
29
+ }
30
+ };
31
+
32
+ const result = await brevit.optimize(testObject);
33
+ if (!result.includes('user.name: Javian') || !result.includes('user.email: support@javianpicardo.com')) {
34
+ throw new Error('Flattened output does not contain expected values');
35
+ }
36
+ });
37
+
38
+ // Test 2: Flatten JSON string
39
+ test('Flatten JSON string', async () => {
40
+ const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
41
+ const brevit = new BrevitClient(config);
42
+
43
+ const jsonString = '{"order": {"orderId": "o-456", "status": "SHIPPED"}}';
44
+ const result = await brevit.optimize(jsonString);
45
+
46
+ if (!result.includes('order.orderId: o-456') || !result.includes('order.status: SHIPPED')) {
47
+ throw new Error('Flattened output does not contain expected values');
48
+ }
49
+ });
50
+
51
+ // Test 3: Short text returns as-is
52
+ test('Short text returns as-is', async () => {
53
+ const config = new BrevitConfig({ longTextThreshold: 500 });
54
+ const brevit = new BrevitClient(config);
55
+
56
+ const shortText = 'Hello World';
57
+ const result = await brevit.optimize(shortText);
58
+
59
+ if (result !== 'Hello World') {
60
+ throw new Error('Short text was modified');
61
+ }
62
+ });
63
+
64
+ // Test 4: Array handling
65
+ test('Array handling', async () => {
66
+ const config = new BrevitConfig({ jsonMode: JsonOptimizationMode.Flatten });
67
+ const brevit = new BrevitClient(config);
68
+
69
+ const testObject = {
70
+ items: [
71
+ { sku: 'A-88', name: 'Brevit Pro' },
72
+ { sku: 'T-22', name: 'Toon Handbook' }
73
+ ]
74
+ };
75
+
76
+ const result = await brevit.optimize(testObject);
77
+ if (!result.includes('items[0].sku: A-88') || !result.includes('items[1].sku: T-22')) {
78
+ throw new Error('Array flattening failed');
79
+ }
80
+ });
81
+
82
+ console.log(`\nTests completed: ${passed} passed, ${failed} failed`);
83
+ process.exit(failed > 0 ? 1 : 0);
84
+ }
85
+
86
+ runTests().catch(error => {
87
+ console.error('Test runner error:', error);
88
+ process.exit(1);
89
+ });
90
+
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "node",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "noEmit": false
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist", "test"]
22
+ }
23
+