tersejson 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1001 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, { get: all[name], enumerable: true });
7
+ };
8
+
9
+ // src/types.ts
10
+ function isTersePayload(value) {
11
+ return typeof value === "object" && value !== null && "__terse__" in value && value.__terse__ === true && "v" in value && "k" in value && "d" in value;
12
+ }
13
+
14
+ // src/core.ts
15
+ function alphaGenerator(index) {
16
+ let key = "";
17
+ let remaining = index;
18
+ do {
19
+ key = String.fromCharCode(97 + remaining % 26) + key;
20
+ remaining = Math.floor(remaining / 26) - 1;
21
+ } while (remaining >= 0);
22
+ return key;
23
+ }
24
+ function numericGenerator(index) {
25
+ return String(index);
26
+ }
27
+ function alphanumericGenerator(index) {
28
+ const letterIndex = Math.floor(index / 9);
29
+ const numIndex = index % 9 + 1;
30
+ return alphaGenerator(letterIndex) + numIndex;
31
+ }
32
+ function shortGenerator(index) {
33
+ if (index === 0) return "_";
34
+ return alphaGenerator(index - 1);
35
+ }
36
+ function prefixedGenerator(prefix, style = "numeric") {
37
+ return (index) => {
38
+ if (style === "alpha") {
39
+ return prefix + alphaGenerator(index);
40
+ }
41
+ return prefix + index;
42
+ };
43
+ }
44
+ function createKeyGenerator(pattern) {
45
+ if (typeof pattern === "function") {
46
+ return { generator: pattern, name: "custom" };
47
+ }
48
+ if (typeof pattern === "string") {
49
+ switch (pattern) {
50
+ case "alpha":
51
+ return { generator: alphaGenerator, name: "alpha" };
52
+ case "numeric":
53
+ return { generator: numericGenerator, name: "numeric" };
54
+ case "alphanumeric":
55
+ return { generator: alphanumericGenerator, name: "alphanumeric" };
56
+ case "short":
57
+ return { generator: shortGenerator, name: "short" };
58
+ case "prefixed":
59
+ return { generator: prefixedGenerator("k"), name: "prefixed:k" };
60
+ default:
61
+ return { generator: alphaGenerator, name: "alpha" };
62
+ }
63
+ }
64
+ if (typeof pattern === "object" && "prefix" in pattern) {
65
+ return {
66
+ generator: prefixedGenerator(pattern.prefix, pattern.style || "numeric"),
67
+ name: `prefixed:${pattern.prefix}`
68
+ };
69
+ }
70
+ return { generator: alphaGenerator, name: "alpha" };
71
+ }
72
+ function resolveNestedDepth(handling, maxDepth) {
73
+ if (handling === void 0 || handling === "deep") {
74
+ return maxDepth;
75
+ }
76
+ if (handling === "shallow") {
77
+ return 1;
78
+ }
79
+ if (handling === "arrays") {
80
+ return maxDepth;
81
+ }
82
+ if (typeof handling === "number") {
83
+ return handling;
84
+ }
85
+ return maxDepth;
86
+ }
87
+ function collectKeys(data, options, currentDepth = 0) {
88
+ const keys = /* @__PURE__ */ new Set();
89
+ const { minKeyLength, maxDepth, nestedHandling, excludeKeys, includeKeys } = options;
90
+ if (currentDepth >= maxDepth) return keys;
91
+ const keyCounts = /* @__PURE__ */ new Map();
92
+ for (const item of data) {
93
+ if (typeof item !== "object" || item === null) continue;
94
+ for (const key of Object.keys(item)) {
95
+ if (excludeKeys?.includes(key)) continue;
96
+ const shouldInclude = includeKeys?.includes(key) || key.length >= minKeyLength;
97
+ if (shouldInclude) {
98
+ keys.add(key);
99
+ keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
100
+ }
101
+ const value = item[key];
102
+ if (nestedHandling === "shallow") {
103
+ continue;
104
+ }
105
+ if (Array.isArray(value) && value.length > 0 && isCompressibleArray(value)) {
106
+ const nestedKeys = collectKeys(
107
+ value,
108
+ options,
109
+ currentDepth + 1
110
+ );
111
+ nestedKeys.forEach((k) => keys.add(k));
112
+ } else if (nestedHandling !== "arrays" && typeof value === "object" && value !== null && !Array.isArray(value)) {
113
+ const nestedKeys = collectKeys(
114
+ [value],
115
+ options,
116
+ currentDepth + 1
117
+ );
118
+ nestedKeys.forEach((k) => keys.add(k));
119
+ }
120
+ }
121
+ }
122
+ if (options.homogeneousOnly && data.length > 0) {
123
+ for (const [key, count] of keyCounts) {
124
+ if (count < data.length) {
125
+ keys.delete(key);
126
+ }
127
+ }
128
+ }
129
+ return keys;
130
+ }
131
+ function isCompressibleArray(data) {
132
+ if (!Array.isArray(data) || data.length === 0) return false;
133
+ return data.every(
134
+ (item) => typeof item === "object" && item !== null && !Array.isArray(item)
135
+ );
136
+ }
137
+ function compressObject(obj, keyToShort, maxDepth, currentDepth = 0) {
138
+ if (currentDepth >= maxDepth) return obj;
139
+ const compressed = {};
140
+ for (const [key, value] of Object.entries(obj)) {
141
+ const shortKey = keyToShort.get(key) ?? key;
142
+ if (Array.isArray(value) && isCompressibleArray(value)) {
143
+ compressed[shortKey] = value.map(
144
+ (item) => compressObject(
145
+ item,
146
+ keyToShort,
147
+ maxDepth,
148
+ currentDepth + 1
149
+ )
150
+ );
151
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
152
+ compressed[shortKey] = compressObject(
153
+ value,
154
+ keyToShort,
155
+ maxDepth,
156
+ currentDepth + 1
157
+ );
158
+ } else {
159
+ compressed[shortKey] = value;
160
+ }
161
+ }
162
+ return compressed;
163
+ }
164
+ function expandObject(obj, shortToKey, maxDepth, currentDepth = 0) {
165
+ if (currentDepth >= maxDepth) return obj;
166
+ const expanded = {};
167
+ for (const [shortKey, value] of Object.entries(obj)) {
168
+ const originalKey = shortToKey.get(shortKey) ?? shortKey;
169
+ if (Array.isArray(value)) {
170
+ expanded[originalKey] = value.map((item) => {
171
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
172
+ return expandObject(
173
+ item,
174
+ shortToKey,
175
+ maxDepth,
176
+ currentDepth + 1
177
+ );
178
+ }
179
+ return item;
180
+ });
181
+ } else if (typeof value === "object" && value !== null) {
182
+ expanded[originalKey] = expandObject(
183
+ value,
184
+ shortToKey,
185
+ maxDepth,
186
+ currentDepth + 1
187
+ );
188
+ } else {
189
+ expanded[originalKey] = value;
190
+ }
191
+ }
192
+ return expanded;
193
+ }
194
+ function compress(data, options = {}) {
195
+ const {
196
+ minKeyLength = 3,
197
+ maxDepth = 10,
198
+ keyPattern = "alpha",
199
+ nestedHandling = "deep",
200
+ homogeneousOnly = false,
201
+ excludeKeys,
202
+ includeKeys
203
+ } = options;
204
+ const { generator, name: patternName } = createKeyGenerator(keyPattern);
205
+ const effectiveDepth = resolveNestedDepth(nestedHandling, maxDepth);
206
+ const allKeys = collectKeys(data, {
207
+ minKeyLength,
208
+ maxDepth: effectiveDepth,
209
+ nestedHandling,
210
+ excludeKeys,
211
+ includeKeys,
212
+ homogeneousOnly
213
+ });
214
+ const sortedKeys = Array.from(allKeys).sort();
215
+ const keyToShort = /* @__PURE__ */ new Map();
216
+ const keyMap = {};
217
+ sortedKeys.forEach((key, index) => {
218
+ const shortKey = generator(index);
219
+ if (shortKey.length < key.length) {
220
+ keyToShort.set(key, shortKey);
221
+ keyMap[shortKey] = key;
222
+ }
223
+ });
224
+ const compressed = data.map(
225
+ (item) => compressObject(item, keyToShort, effectiveDepth)
226
+ );
227
+ return {
228
+ __terse__: true,
229
+ v: 1,
230
+ k: keyMap,
231
+ d: compressed,
232
+ p: patternName
233
+ };
234
+ }
235
+ function expand(payload) {
236
+ const shortToKey = new Map(
237
+ Object.entries(payload.k).map(([short, original]) => [short, original])
238
+ );
239
+ if (Array.isArray(payload.d)) {
240
+ return payload.d.map((item) => {
241
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
242
+ return expandObject(item, shortToKey, 10);
243
+ }
244
+ return item;
245
+ });
246
+ }
247
+ if (typeof payload.d === "object" && payload.d !== null) {
248
+ return expandObject(payload.d, shortToKey, 10);
249
+ }
250
+ return payload.d;
251
+ }
252
+ function createTerseProxy(compressed, keyMap) {
253
+ const originalToShort = new Map(
254
+ Object.entries(keyMap).map(([short, original]) => [original, short])
255
+ );
256
+ const handler = {
257
+ get(target, prop) {
258
+ if (typeof prop === "symbol") {
259
+ return Reflect.get(target, prop);
260
+ }
261
+ const shortKey = originalToShort.get(prop);
262
+ const actualKey = shortKey ?? prop;
263
+ const value = target[actualKey];
264
+ if (Array.isArray(value)) {
265
+ return value.map((item) => {
266
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
267
+ return createTerseProxy(item, keyMap);
268
+ }
269
+ return item;
270
+ });
271
+ }
272
+ if (typeof value === "object" && value !== null) {
273
+ return createTerseProxy(value, keyMap);
274
+ }
275
+ return value;
276
+ },
277
+ has(target, prop) {
278
+ if (typeof prop === "symbol") {
279
+ return Reflect.has(target, prop);
280
+ }
281
+ const shortKey = originalToShort.get(prop);
282
+ return (shortKey ?? prop) in target;
283
+ },
284
+ ownKeys(target) {
285
+ return Object.keys(target).map((shortKey) => keyMap[shortKey] ?? shortKey);
286
+ },
287
+ getOwnPropertyDescriptor(target, prop) {
288
+ if (typeof prop === "symbol") {
289
+ return Reflect.getOwnPropertyDescriptor(target, prop);
290
+ }
291
+ const shortKey = originalToShort.get(prop);
292
+ const actualKey = shortKey ?? prop;
293
+ const descriptor = Object.getOwnPropertyDescriptor(target, actualKey);
294
+ if (descriptor) {
295
+ return { ...descriptor, enumerable: true, configurable: true };
296
+ }
297
+ return void 0;
298
+ }
299
+ };
300
+ return new Proxy(compressed, handler);
301
+ }
302
+ function wrapWithProxy(payload) {
303
+ if (Array.isArray(payload.d)) {
304
+ return payload.d.map((item) => {
305
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
306
+ return createTerseProxy(item, payload.k);
307
+ }
308
+ return item;
309
+ });
310
+ }
311
+ if (typeof payload.d === "object" && payload.d !== null) {
312
+ return createTerseProxy(payload.d, payload.k);
313
+ }
314
+ return payload.d;
315
+ }
316
+
317
+ // src/express.ts
318
+ var express_exports = {};
319
+ __export(express_exports, {
320
+ TerseAnalytics: () => TerseAnalytics,
321
+ analytics: () => analytics,
322
+ default: () => express_default,
323
+ getAnalytics: () => getAnalytics,
324
+ initAnalytics: () => initAnalytics,
325
+ terse: () => terse,
326
+ terseQueryParam: () => terseQueryParam
327
+ });
328
+
329
+ // src/analytics.ts
330
+ var DEFAULT_CONFIG = {
331
+ enabled: false,
332
+ reportToCloud: false,
333
+ reportInterval: 6e4,
334
+ trackEndpoints: false,
335
+ endpoint: "https://api.tersejson.com/v1/analytics",
336
+ debug: false
337
+ };
338
+ var TerseAnalytics = class {
339
+ constructor(config = {}) {
340
+ this.events = [];
341
+ this.config = { ...DEFAULT_CONFIG, ...config };
342
+ this.isNode = typeof window === "undefined";
343
+ this.stats = this.createEmptyStats();
344
+ if (this.config.enabled && this.config.reportToCloud) {
345
+ this.startReporting();
346
+ }
347
+ }
348
+ /**
349
+ * Create empty stats object
350
+ */
351
+ createEmptyStats() {
352
+ return {
353
+ totalEvents: 0,
354
+ totalOriginalBytes: 0,
355
+ totalCompressedBytes: 0,
356
+ totalBytesSaved: 0,
357
+ averageRatio: 0,
358
+ totalObjects: 0,
359
+ sessionStart: Date.now(),
360
+ lastEvent: Date.now()
361
+ };
362
+ }
363
+ /**
364
+ * Record a compression event
365
+ */
366
+ record(event) {
367
+ if (!this.config.enabled) return;
368
+ const fullEvent = {
369
+ ...event,
370
+ timestamp: Date.now(),
371
+ // Hash endpoint for privacy if tracking is enabled
372
+ endpoint: this.config.trackEndpoints && event.endpoint ? this.hashEndpoint(event.endpoint) : void 0
373
+ };
374
+ this.stats.totalEvents++;
375
+ this.stats.totalOriginalBytes += event.originalSize;
376
+ this.stats.totalCompressedBytes += event.compressedSize;
377
+ this.stats.totalBytesSaved += event.originalSize - event.compressedSize;
378
+ this.stats.totalObjects += event.objectCount;
379
+ this.stats.lastEvent = fullEvent.timestamp;
380
+ this.stats.averageRatio = this.stats.totalCompressedBytes / this.stats.totalOriginalBytes;
381
+ this.events.push(fullEvent);
382
+ if (this.config.onEvent) {
383
+ this.config.onEvent(fullEvent);
384
+ }
385
+ if (this.config.debug) {
386
+ const savings = ((1 - event.compressedSize / event.originalSize) * 100).toFixed(1);
387
+ console.log(`[tersejson:analytics] ${event.originalSize} \u2192 ${event.compressedSize} bytes (${savings}% saved)`);
388
+ }
389
+ }
390
+ /**
391
+ * Get current stats
392
+ */
393
+ getStats() {
394
+ return { ...this.stats };
395
+ }
396
+ /**
397
+ * Get formatted stats summary
398
+ */
399
+ getSummary() {
400
+ const stats = this.stats;
401
+ const savedKB = (stats.totalBytesSaved / 1024).toFixed(2);
402
+ const savedPercent = stats.totalOriginalBytes > 0 ? ((1 - stats.averageRatio) * 100).toFixed(1) : "0";
403
+ return `TerseJSON Stats: ${stats.totalEvents} compressions, ${savedKB}KB saved (${savedPercent}% avg)`;
404
+ }
405
+ /**
406
+ * Reset stats
407
+ */
408
+ reset() {
409
+ this.events = [];
410
+ this.stats = this.createEmptyStats();
411
+ }
412
+ /**
413
+ * Start periodic reporting to cloud
414
+ */
415
+ startReporting() {
416
+ if (this.reportTimer) return;
417
+ this.reportTimer = setInterval(() => {
418
+ this.reportToCloud();
419
+ }, this.config.reportInterval);
420
+ if (this.isNode && typeof process !== "undefined") {
421
+ process.on("beforeExit", () => this.reportToCloud());
422
+ }
423
+ }
424
+ /**
425
+ * Stop reporting
426
+ */
427
+ stop() {
428
+ if (this.reportTimer) {
429
+ clearInterval(this.reportTimer);
430
+ this.reportTimer = void 0;
431
+ }
432
+ }
433
+ /**
434
+ * Report stats to tersejson.com
435
+ */
436
+ async reportToCloud() {
437
+ if (!this.config.reportToCloud || this.events.length === 0) return;
438
+ const payload = {
439
+ apiKey: this.config.apiKey,
440
+ projectId: this.config.projectId,
441
+ stats: this.stats,
442
+ events: this.events.slice(-100),
443
+ // Last 100 events only
444
+ meta: {
445
+ version: "0.1.0",
446
+ runtime: this.isNode ? "node" : "browser"
447
+ }
448
+ };
449
+ try {
450
+ const fetchFn = this.isNode ? (await import('https')).request : globalThis.fetch;
451
+ if (this.isNode) {
452
+ const url = new URL(this.config.endpoint);
453
+ const req = fetchFn({
454
+ hostname: url.hostname,
455
+ port: url.port || 443,
456
+ path: url.pathname,
457
+ method: "POST",
458
+ headers: {
459
+ "Content-Type": "application/json"
460
+ }
461
+ });
462
+ req.write(JSON.stringify(payload));
463
+ req.end();
464
+ } else {
465
+ fetchFn(this.config.endpoint, {
466
+ method: "POST",
467
+ headers: { "Content-Type": "application/json" },
468
+ body: JSON.stringify(payload),
469
+ keepalive: true
470
+ // Allow sending on page unload
471
+ }).catch(() => {
472
+ });
473
+ }
474
+ this.events = [];
475
+ if (this.config.onStats) {
476
+ this.config.onStats(this.stats);
477
+ }
478
+ if (this.config.debug) {
479
+ console.log("[tersejson:analytics] Reported stats to cloud");
480
+ }
481
+ } catch {
482
+ if (this.config.debug) {
483
+ console.log("[tersejson:analytics] Failed to report stats");
484
+ }
485
+ }
486
+ }
487
+ /**
488
+ * Hash endpoint for privacy
489
+ */
490
+ hashEndpoint(endpoint) {
491
+ return endpoint.replace(/\/\d+/g, "/:id").replace(/\/[a-f0-9-]{36}/gi, "/:uuid").replace(/\?.*$/, "");
492
+ }
493
+ };
494
+ var globalAnalytics = null;
495
+ function initAnalytics(config) {
496
+ globalAnalytics = new TerseAnalytics(config);
497
+ return globalAnalytics;
498
+ }
499
+ function getAnalytics() {
500
+ return globalAnalytics;
501
+ }
502
+ function recordEvent(event) {
503
+ globalAnalytics?.record(event);
504
+ }
505
+ var analytics = {
506
+ /**
507
+ * Enable local-only analytics (no cloud reporting)
508
+ */
509
+ local(options = {}) {
510
+ return initAnalytics({
511
+ enabled: true,
512
+ reportToCloud: false,
513
+ debug: options.debug,
514
+ onEvent: options.onEvent
515
+ });
516
+ },
517
+ /**
518
+ * Enable cloud analytics with API key
519
+ */
520
+ cloud(apiKey, options = {}) {
521
+ return initAnalytics({
522
+ ...options,
523
+ enabled: true,
524
+ reportToCloud: true,
525
+ apiKey
526
+ });
527
+ },
528
+ /**
529
+ * Get current stats
530
+ */
531
+ getStats() {
532
+ return globalAnalytics?.getStats() ?? null;
533
+ },
534
+ /**
535
+ * Get formatted summary
536
+ */
537
+ getSummary() {
538
+ return globalAnalytics?.getSummary() ?? "Analytics not initialized";
539
+ }
540
+ };
541
+
542
+ // src/express.ts
543
+ var DEFAULT_OPTIONS = {
544
+ // Middleware-specific options
545
+ minArrayLength: 2,
546
+ shouldCompress: () => true,
547
+ headerName: "x-terse-json",
548
+ debug: false,
549
+ // CompressOptions
550
+ minKeyLength: 3,
551
+ maxDepth: 10,
552
+ keyPattern: "alpha",
553
+ nestedHandling: "deep",
554
+ homogeneousOnly: false,
555
+ excludeKeys: [],
556
+ includeKeys: []
557
+ };
558
+ function terse(options = {}) {
559
+ const { analytics: analyticsOption, ...restOptions } = options;
560
+ const config = { ...DEFAULT_OPTIONS, ...restOptions };
561
+ let analyticsInstance = null;
562
+ if (analyticsOption) {
563
+ if (analyticsOption instanceof TerseAnalytics) {
564
+ analyticsInstance = analyticsOption;
565
+ } else if (analyticsOption === true) {
566
+ analyticsInstance = new TerseAnalytics({ enabled: true, debug: config.debug });
567
+ } else if (typeof analyticsOption === "object") {
568
+ analyticsInstance = new TerseAnalytics({ enabled: true, ...analyticsOption });
569
+ }
570
+ }
571
+ return function terseMiddleware(req, res, next) {
572
+ const acceptsTerse = req.headers["accept-terse"] === "true" || req.headers["x-accept-terse"] === "true";
573
+ const originalJson = res.json.bind(res);
574
+ res.json = function terseJson(data) {
575
+ if (!acceptsTerse) {
576
+ return originalJson(data);
577
+ }
578
+ if (!isCompressibleArray(data)) {
579
+ return originalJson(data);
580
+ }
581
+ if (data.length < config.minArrayLength) {
582
+ return originalJson(data);
583
+ }
584
+ if (!config.shouldCompress(data, req)) {
585
+ return originalJson(data);
586
+ }
587
+ try {
588
+ const dataArray = data;
589
+ const compressed = compress(dataArray, {
590
+ minKeyLength: config.minKeyLength,
591
+ maxDepth: config.maxDepth,
592
+ keyPattern: config.keyPattern,
593
+ nestedHandling: config.nestedHandling,
594
+ homogeneousOnly: config.homogeneousOnly,
595
+ excludeKeys: config.excludeKeys,
596
+ includeKeys: config.includeKeys
597
+ });
598
+ const originalSize = JSON.stringify(data).length;
599
+ const compressedSize = JSON.stringify(compressed).length;
600
+ if (analyticsInstance) {
601
+ analyticsInstance.record({
602
+ originalSize,
603
+ compressedSize,
604
+ objectCount: dataArray.length,
605
+ keysCompressed: Object.keys(compressed.k).length,
606
+ endpoint: req.path,
607
+ keyPattern: compressed.p || "alpha"
608
+ });
609
+ }
610
+ recordEvent({
611
+ originalSize,
612
+ compressedSize,
613
+ objectCount: dataArray.length,
614
+ keysCompressed: Object.keys(compressed.k).length,
615
+ endpoint: req.path,
616
+ keyPattern: compressed.p || "alpha"
617
+ });
618
+ if (config.debug) {
619
+ const savings = ((1 - compressedSize / originalSize) * 100).toFixed(1);
620
+ console.log(
621
+ `[tersejson] Compressed ${originalSize} -> ${compressedSize} bytes (${savings}% savings)`
622
+ );
623
+ }
624
+ res.setHeader(config.headerName, "true");
625
+ return originalJson(compressed);
626
+ } catch (error) {
627
+ if (config.debug) {
628
+ console.error("[tersejson] Compression failed:", error);
629
+ }
630
+ return originalJson(data);
631
+ }
632
+ };
633
+ next();
634
+ };
635
+ }
636
+ function terseQueryParam(paramName = "terse") {
637
+ return function terseQueryMiddleware(req, _res, next) {
638
+ if (req.query[paramName] === "true") {
639
+ req.headers["accept-terse"] = "true";
640
+ }
641
+ next();
642
+ };
643
+ }
644
+ var express_default = terse;
645
+
646
+ // src/client.ts
647
+ var client_exports = {};
648
+ __export(client_exports, {
649
+ axiosInterceptor: () => axiosInterceptor,
650
+ createFetch: () => createFetch,
651
+ default: () => client_default,
652
+ expand: () => expand,
653
+ fetch: () => fetch,
654
+ isTersePayload: () => isTersePayload,
655
+ process: () => process2,
656
+ proxy: () => wrapWithProxy,
657
+ useTerseFetch: () => useTerseFetch
658
+ });
659
+ var DEFAULT_OPTIONS2 = {
660
+ headerName: "x-terse-json",
661
+ debug: false,
662
+ autoExpand: true
663
+ };
664
+ function process2(data, options = {}) {
665
+ const { useProxy = true } = options;
666
+ if (isTersePayload(data)) {
667
+ return useProxy ? wrapWithProxy(data) : expand(data);
668
+ }
669
+ return data;
670
+ }
671
+ function createFetch(options = {}) {
672
+ const config = { ...DEFAULT_OPTIONS2, ...options };
673
+ return async function terseFetch(input, init = {}) {
674
+ const headers = new Headers(init.headers);
675
+ headers.set("accept-terse", "true");
676
+ const response = await globalThis.fetch(input, {
677
+ ...init,
678
+ headers
679
+ });
680
+ const isTerse = response.headers.get(config.headerName) === "true";
681
+ if (!isTerse || !config.autoExpand) {
682
+ return response;
683
+ }
684
+ const clonedResponse = response.clone();
685
+ return new Proxy(response, {
686
+ get(target, prop) {
687
+ if (prop === "json") {
688
+ return async function() {
689
+ const data = await clonedResponse.json();
690
+ if (isTersePayload(data)) {
691
+ if (config.debug) {
692
+ console.log("[tersejson] Expanding terse response");
693
+ }
694
+ return wrapWithProxy(data);
695
+ }
696
+ return data;
697
+ };
698
+ }
699
+ const value = Reflect.get(target, prop);
700
+ return typeof value === "function" ? value.bind(target) : value;
701
+ }
702
+ });
703
+ };
704
+ }
705
+ var fetch = createFetch();
706
+ function useTerseFetch(options = {}) {
707
+ return createFetch(options);
708
+ }
709
+ var axiosInterceptor = {
710
+ request: (config) => {
711
+ config.headers = config.headers || {};
712
+ config.headers["accept-terse"] = "true";
713
+ return config;
714
+ },
715
+ response: (response) => {
716
+ const isTerse = response.headers?.["x-terse-json"] === "true";
717
+ if (isTerse && isTersePayload(response.data)) {
718
+ response.data = wrapWithProxy(response.data);
719
+ }
720
+ return response;
721
+ }
722
+ };
723
+ var client_default = { fetch, createFetch, expand, proxy: wrapWithProxy, process: process2 };
724
+
725
+ // src/integrations.ts
726
+ var integrations_exports = {};
727
+ __export(integrations_exports, {
728
+ angularInterceptorSnippet: () => angularInterceptorSnippet,
729
+ angularJSInterceptorSnippet: () => angularJSInterceptorSnippet,
730
+ axiosInterceptors: () => axiosInterceptors,
731
+ createAngularInterceptor: () => createAngularInterceptor,
732
+ createAngularJSInterceptor: () => createAngularJSInterceptor,
733
+ createAxiosInterceptors: () => createAxiosInterceptors,
734
+ createQueryFn: () => createQueryFn,
735
+ createSWRFetcher: () => createSWRFetcher,
736
+ default: () => integrations_default,
737
+ jQuerySetupSnippet: () => jQuerySetupSnippet,
738
+ setupJQueryAjax: () => setupJQueryAjax
739
+ });
740
+ function createAxiosInterceptors(options = {}) {
741
+ const { useProxy = true, debug = false } = options;
742
+ return {
743
+ request: (config) => {
744
+ if (config.headers) {
745
+ if (typeof config.headers.set === "function") {
746
+ config.headers.set("accept-terse", "true");
747
+ } else {
748
+ config.headers["accept-terse"] = "true";
749
+ }
750
+ } else {
751
+ config.headers = { "accept-terse": "true" };
752
+ }
753
+ return config;
754
+ },
755
+ response: (response) => {
756
+ let isTerse = false;
757
+ if (response.headers) {
758
+ if (typeof response.headers.get === "function") {
759
+ isTerse = response.headers.get("x-terse-json") === "true";
760
+ } else {
761
+ isTerse = response.headers["x-terse-json"] === "true";
762
+ }
763
+ }
764
+ if (isTerse && isTersePayload(response.data)) {
765
+ if (debug) {
766
+ console.log("[tersejson] Expanding Axios response");
767
+ }
768
+ response.data = useProxy ? wrapWithProxy(response.data) : expand(response.data);
769
+ }
770
+ return response;
771
+ }
772
+ };
773
+ }
774
+ var axiosInterceptors = createAxiosInterceptors();
775
+ function createAngularInterceptor(options = {}) {
776
+ const { useProxy = true, debug = false } = options;
777
+ return {
778
+ /**
779
+ * Modifies the request to include the accept-terse header
780
+ */
781
+ modifyRequest: (req) => {
782
+ return req.clone({
783
+ setHeaders: {
784
+ "accept-terse": "true"
785
+ }
786
+ });
787
+ },
788
+ /**
789
+ * Processes the response to expand TerseJSON payloads
790
+ */
791
+ processResponse: (event) => {
792
+ if (event.type === 4 && event.body !== void 0) {
793
+ const isTerse = event.headers?.get("x-terse-json") === "true";
794
+ if (isTerse && isTersePayload(event.body)) {
795
+ if (debug) {
796
+ console.log("[tersejson] Expanding Angular response");
797
+ }
798
+ return {
799
+ ...event,
800
+ body: useProxy ? wrapWithProxy(event.body) : expand(event.body)
801
+ };
802
+ }
803
+ }
804
+ return event;
805
+ }
806
+ };
807
+ }
808
+ var angularInterceptorSnippet = `
809
+ // terse.interceptor.ts
810
+ import { Injectable } from '@angular/core';
811
+ import {
812
+ HttpInterceptor,
813
+ HttpRequest,
814
+ HttpHandler,
815
+ HttpEvent,
816
+ HttpResponse
817
+ } from '@angular/common/http';
818
+ import { Observable } from 'rxjs';
819
+ import { map } from 'rxjs/operators';
820
+ import { isTersePayload, wrapWithProxy } from 'tersejson';
821
+
822
+ @Injectable()
823
+ export class TerseInterceptor implements HttpInterceptor {
824
+ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
825
+ // Add accept-terse header
826
+ const terseReq = req.clone({
827
+ setHeaders: { 'accept-terse': 'true' }
828
+ });
829
+
830
+ return next.handle(terseReq).pipe(
831
+ map(event => {
832
+ if (event instanceof HttpResponse && event.body) {
833
+ const isTerse = event.headers.get('x-terse-json') === 'true';
834
+ if (isTerse && isTersePayload(event.body)) {
835
+ return event.clone({ body: wrapWithProxy(event.body) });
836
+ }
837
+ }
838
+ return event;
839
+ })
840
+ );
841
+ }
842
+ }
843
+
844
+ // app.module.ts - Add to providers:
845
+ // { provide: HTTP_INTERCEPTORS, useClass: TerseInterceptor, multi: true }
846
+ `;
847
+ function createAngularJSInterceptor(options = {}) {
848
+ const { useProxy = true, debug = false } = options;
849
+ return {
850
+ /**
851
+ * Request interceptor - adds accept-terse header
852
+ */
853
+ request: (config) => {
854
+ config.headers = config.headers || {};
855
+ config.headers["accept-terse"] = "true";
856
+ return config;
857
+ },
858
+ /**
859
+ * Response interceptor - expands TerseJSON payloads
860
+ */
861
+ response: (response) => {
862
+ const isTerse = response.headers("x-terse-json") === "true";
863
+ if (isTerse && isTersePayload(response.data)) {
864
+ if (debug) {
865
+ console.log("[tersejson] Expanding AngularJS response");
866
+ }
867
+ response.data = useProxy ? wrapWithProxy(response.data) : expand(response.data);
868
+ }
869
+ return response;
870
+ }
871
+ };
872
+ }
873
+ var angularJSInterceptorSnippet = `
874
+ // Setup TerseJSON with AngularJS
875
+ angular.module('myApp', [])
876
+ .factory('terseInterceptor', ['$q', function($q) {
877
+ return {
878
+ request: function(config) {
879
+ config.headers = config.headers || {};
880
+ config.headers['accept-terse'] = 'true';
881
+ return config;
882
+ },
883
+ response: function(response) {
884
+ var isTerse = response.headers('x-terse-json') === 'true';
885
+ if (isTerse && response.data && response.data.__terse__) {
886
+ // Use tersejson.process() or tersejson.wrapWithProxy()
887
+ response.data = tersejson.process(response.data);
888
+ }
889
+ return response;
890
+ }
891
+ };
892
+ }])
893
+ .config(['$httpProvider', function($httpProvider) {
894
+ $httpProvider.interceptors.push('terseInterceptor');
895
+ }]);
896
+ `;
897
+ function setupJQueryAjax($, options = {}) {
898
+ const { useProxy = true, debug = false } = options;
899
+ $.ajaxSetup({
900
+ headers: {
901
+ "accept-terse": "true"
902
+ }
903
+ });
904
+ $.ajaxPrefilter((ajaxOptions) => {
905
+ const originalDataFilter = ajaxOptions.dataFilter;
906
+ ajaxOptions.dataFilter = function(data, type) {
907
+ let processed = data;
908
+ if (originalDataFilter) {
909
+ processed = originalDataFilter(data, type);
910
+ }
911
+ if (type === "json" || type === void 0) {
912
+ try {
913
+ const parsed = typeof processed === "string" ? JSON.parse(processed) : processed;
914
+ if (isTersePayload(parsed)) {
915
+ if (debug) {
916
+ console.log("[tersejson] Expanding jQuery response");
917
+ }
918
+ return useProxy ? wrapWithProxy(parsed) : expand(parsed);
919
+ }
920
+ return parsed;
921
+ } catch {
922
+ return processed;
923
+ }
924
+ }
925
+ return processed;
926
+ };
927
+ });
928
+ }
929
+ var jQuerySetupSnippet = `
930
+ // Setup TerseJSON with jQuery
931
+ $.ajaxSetup({
932
+ headers: { 'accept-terse': 'true' },
933
+ dataFilter: function(data, type) {
934
+ if (type === 'json') {
935
+ var parsed = JSON.parse(data);
936
+ if (parsed && parsed.__terse__) {
937
+ return tersejson.process(parsed);
938
+ }
939
+ return parsed;
940
+ }
941
+ return data;
942
+ }
943
+ });
944
+ `;
945
+ function createSWRFetcher(options = {}) {
946
+ const { useProxy = true, debug = false } = options;
947
+ return async (url) => {
948
+ const response = await globalThis.fetch(url, {
949
+ headers: {
950
+ "accept-terse": "true"
951
+ }
952
+ });
953
+ if (!response.ok) {
954
+ throw new Error(`HTTP error! status: ${response.status}`);
955
+ }
956
+ const data = await response.json();
957
+ if (isTersePayload(data)) {
958
+ if (debug) {
959
+ console.log("[tersejson] Expanding SWR response");
960
+ }
961
+ return useProxy ? wrapWithProxy(data) : expand(data);
962
+ }
963
+ return data;
964
+ };
965
+ }
966
+ var createQueryFn = createSWRFetcher;
967
+ var integrations_default = {
968
+ // Axios
969
+ createAxiosInterceptors,
970
+ axiosInterceptors,
971
+ // Angular
972
+ createAngularInterceptor,
973
+ angularInterceptorSnippet,
974
+ // AngularJS
975
+ createAngularJSInterceptor,
976
+ angularJSInterceptorSnippet,
977
+ // jQuery
978
+ setupJQueryAjax,
979
+ jQuerySetupSnippet,
980
+ // SWR / React Query
981
+ createSWRFetcher,
982
+ createQueryFn
983
+ };
984
+
985
+ exports.TerseAnalytics = TerseAnalytics;
986
+ exports.analytics = analytics;
987
+ exports.client = client_exports;
988
+ exports.compress = compress;
989
+ exports.createKeyGenerator = createKeyGenerator;
990
+ exports.createTerseProxy = createTerseProxy;
991
+ exports.expand = expand;
992
+ exports.express = express_exports;
993
+ exports.getAnalytics = getAnalytics;
994
+ exports.initAnalytics = initAnalytics;
995
+ exports.integrations = integrations_exports;
996
+ exports.isCompressibleArray = isCompressibleArray;
997
+ exports.isTersePayload = isTersePayload;
998
+ exports.recordEvent = recordEvent;
999
+ exports.wrapWithProxy = wrapWithProxy;
1000
+ //# sourceMappingURL=index.js.map
1001
+ //# sourceMappingURL=index.js.map