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