observability-toolkit 1.8.0 → 1.8.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.
Files changed (186) hide show
  1. package/README.md +81 -3
  2. package/dist/backends/index.d.ts +119 -0
  3. package/dist/backends/index.d.ts.map +1 -1
  4. package/dist/backends/index.js +57 -0
  5. package/dist/backends/index.js.map +1 -1
  6. package/dist/backends/index.test.d.ts +5 -0
  7. package/dist/backends/index.test.d.ts.map +1 -0
  8. package/dist/backends/index.test.js +156 -0
  9. package/dist/backends/index.test.js.map +1 -0
  10. package/dist/backends/local-jsonl-boolean-search.test.js +8 -27
  11. package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
  12. package/dist/backends/local-jsonl-logs.test.d.ts +2 -0
  13. package/dist/backends/local-jsonl-logs.test.d.ts.map +1 -0
  14. package/dist/backends/local-jsonl-logs.test.js +603 -0
  15. package/dist/backends/local-jsonl-logs.test.js.map +1 -0
  16. package/dist/backends/local-jsonl-traces.test.d.ts +2 -0
  17. package/dist/backends/local-jsonl-traces.test.d.ts.map +1 -0
  18. package/dist/backends/local-jsonl-traces.test.js +1723 -0
  19. package/dist/backends/local-jsonl-traces.test.js.map +1 -0
  20. package/dist/backends/local-jsonl.d.ts +4 -1
  21. package/dist/backends/local-jsonl.d.ts.map +1 -1
  22. package/dist/backends/local-jsonl.js +185 -1
  23. package/dist/backends/local-jsonl.js.map +1 -1
  24. package/dist/backends/local-jsonl.test.js +723 -46
  25. package/dist/backends/local-jsonl.test.js.map +1 -1
  26. package/dist/backends/signoz-api.d.ts +32 -0
  27. package/dist/backends/signoz-api.d.ts.map +1 -1
  28. package/dist/backends/signoz-api.js +231 -33
  29. package/dist/backends/signoz-api.js.map +1 -1
  30. package/dist/backends/signoz-api.test.js +410 -63
  31. package/dist/backends/signoz-api.test.js.map +1 -1
  32. package/dist/lib/constants.d.ts +59 -0
  33. package/dist/lib/constants.d.ts.map +1 -1
  34. package/dist/lib/constants.js +252 -6
  35. package/dist/lib/constants.js.map +1 -1
  36. package/dist/lib/constants.test.js +357 -21
  37. package/dist/lib/constants.test.js.map +1 -1
  38. package/dist/lib/edge-cases.test.d.ts +11 -0
  39. package/dist/lib/edge-cases.test.d.ts.map +1 -0
  40. package/dist/lib/edge-cases.test.js +634 -0
  41. package/dist/lib/edge-cases.test.js.map +1 -0
  42. package/dist/lib/error-sanitizer.d.ts +57 -0
  43. package/dist/lib/error-sanitizer.d.ts.map +1 -0
  44. package/dist/lib/error-sanitizer.js +207 -0
  45. package/dist/lib/error-sanitizer.js.map +1 -0
  46. package/dist/lib/error-sanitizer.test.d.ts +8 -0
  47. package/dist/lib/error-sanitizer.test.d.ts.map +1 -0
  48. package/dist/lib/error-sanitizer.test.js +369 -0
  49. package/dist/lib/error-sanitizer.test.js.map +1 -0
  50. package/dist/lib/file-utils.d.ts +134 -0
  51. package/dist/lib/file-utils.d.ts.map +1 -1
  52. package/dist/lib/file-utils.js +395 -9
  53. package/dist/lib/file-utils.js.map +1 -1
  54. package/dist/lib/file-utils.test.js +444 -3
  55. package/dist/lib/file-utils.test.js.map +1 -1
  56. package/dist/lib/indexer.d.ts +9 -1
  57. package/dist/lib/indexer.d.ts.map +1 -1
  58. package/dist/lib/indexer.js +51 -2
  59. package/dist/lib/indexer.js.map +1 -1
  60. package/dist/lib/indexer.test.js +138 -20
  61. package/dist/lib/indexer.test.js.map +1 -1
  62. package/dist/lib/input-validator.d.ts +103 -0
  63. package/dist/lib/input-validator.d.ts.map +1 -0
  64. package/dist/lib/input-validator.js +250 -0
  65. package/dist/lib/input-validator.js.map +1 -0
  66. package/dist/lib/input-validator.test.d.ts +2 -0
  67. package/dist/lib/input-validator.test.d.ts.map +1 -0
  68. package/dist/lib/input-validator.test.js +287 -0
  69. package/dist/lib/input-validator.test.js.map +1 -0
  70. package/dist/lib/query-sanitizer.d.ts +143 -0
  71. package/dist/lib/query-sanitizer.d.ts.map +1 -0
  72. package/dist/lib/query-sanitizer.js +261 -0
  73. package/dist/lib/query-sanitizer.js.map +1 -0
  74. package/dist/lib/query-sanitizer.test.d.ts +5 -0
  75. package/dist/lib/query-sanitizer.test.d.ts.map +1 -0
  76. package/dist/lib/query-sanitizer.test.js +400 -0
  77. package/dist/lib/query-sanitizer.test.js.map +1 -0
  78. package/dist/lib/server-utils.d.ts +80 -0
  79. package/dist/lib/server-utils.d.ts.map +1 -0
  80. package/dist/lib/server-utils.js +141 -0
  81. package/dist/lib/server-utils.js.map +1 -0
  82. package/dist/lib/shared-schemas.d.ts +59 -0
  83. package/dist/lib/shared-schemas.d.ts.map +1 -0
  84. package/dist/lib/shared-schemas.js +58 -0
  85. package/dist/lib/shared-schemas.js.map +1 -0
  86. package/dist/lib/shared-schemas.test.d.ts +5 -0
  87. package/dist/lib/shared-schemas.test.d.ts.map +1 -0
  88. package/dist/lib/shared-schemas.test.js +106 -0
  89. package/dist/lib/shared-schemas.test.js.map +1 -0
  90. package/dist/lib/toon-encoder.d.ts +21 -0
  91. package/dist/lib/toon-encoder.d.ts.map +1 -0
  92. package/dist/lib/toon-encoder.js +46 -0
  93. package/dist/lib/toon-encoder.js.map +1 -0
  94. package/dist/server.d.ts +1 -1
  95. package/dist/server.d.ts.map +1 -1
  96. package/dist/server.js +155 -81
  97. package/dist/server.js.map +1 -1
  98. package/dist/server.test.js +363 -0
  99. package/dist/server.test.js.map +1 -1
  100. package/dist/test-helpers/env-utils.d.ts +65 -0
  101. package/dist/test-helpers/env-utils.d.ts.map +1 -0
  102. package/dist/test-helpers/env-utils.js +94 -0
  103. package/dist/test-helpers/env-utils.js.map +1 -0
  104. package/dist/test-helpers/file-utils.d.ts +93 -0
  105. package/dist/test-helpers/file-utils.d.ts.map +1 -0
  106. package/dist/test-helpers/file-utils.js +206 -0
  107. package/dist/test-helpers/file-utils.js.map +1 -0
  108. package/dist/test-helpers/index.d.ts +10 -0
  109. package/dist/test-helpers/index.d.ts.map +1 -0
  110. package/dist/test-helpers/index.js +28 -0
  111. package/dist/test-helpers/index.js.map +1 -0
  112. package/dist/test-helpers/mock-backends.d.ts +139 -0
  113. package/dist/test-helpers/mock-backends.d.ts.map +1 -0
  114. package/dist/test-helpers/mock-backends.js +227 -0
  115. package/dist/test-helpers/mock-backends.js.map +1 -0
  116. package/dist/test-helpers/mock-backends.test.d.ts +5 -0
  117. package/dist/test-helpers/mock-backends.test.d.ts.map +1 -0
  118. package/dist/test-helpers/mock-backends.test.js +368 -0
  119. package/dist/test-helpers/mock-backends.test.js.map +1 -0
  120. package/dist/test-helpers/schema-validators.d.ts +32 -0
  121. package/dist/test-helpers/schema-validators.d.ts.map +1 -0
  122. package/dist/test-helpers/schema-validators.js +125 -0
  123. package/dist/test-helpers/schema-validators.js.map +1 -0
  124. package/dist/test-helpers/test-data-builders.d.ts +223 -0
  125. package/dist/test-helpers/test-data-builders.d.ts.map +1 -0
  126. package/dist/test-helpers/test-data-builders.js +288 -0
  127. package/dist/test-helpers/test-data-builders.js.map +1 -0
  128. package/dist/test-helpers/test-data-builders.test.d.ts +2 -0
  129. package/dist/test-helpers/test-data-builders.test.d.ts.map +1 -0
  130. package/dist/test-helpers/test-data-builders.test.js +306 -0
  131. package/dist/test-helpers/test-data-builders.test.js.map +1 -0
  132. package/dist/test-helpers/tool-validators.d.ts +28 -0
  133. package/dist/test-helpers/tool-validators.d.ts.map +1 -0
  134. package/dist/test-helpers/tool-validators.js +56 -0
  135. package/dist/test-helpers/tool-validators.js.map +1 -0
  136. package/dist/tools/context-stats.d.ts +1 -0
  137. package/dist/tools/context-stats.d.ts.map +1 -1
  138. package/dist/tools/context-stats.js +9 -5
  139. package/dist/tools/context-stats.js.map +1 -1
  140. package/dist/tools/context-stats.test.js +24 -10
  141. package/dist/tools/context-stats.test.js.map +1 -1
  142. package/dist/tools/get-trace-url.js +2 -2
  143. package/dist/tools/get-trace-url.js.map +1 -1
  144. package/dist/tools/health-check.js +2 -2
  145. package/dist/tools/health-check.js.map +1 -1
  146. package/dist/tools/index.d.ts +1 -0
  147. package/dist/tools/index.d.ts.map +1 -1
  148. package/dist/tools/index.js +1 -0
  149. package/dist/tools/index.js.map +1 -1
  150. package/dist/tools/query-evaluations.d.ts +186 -0
  151. package/dist/tools/query-evaluations.d.ts.map +1 -0
  152. package/dist/tools/query-evaluations.js +351 -0
  153. package/dist/tools/query-evaluations.js.map +1 -0
  154. package/dist/tools/query-evaluations.test.d.ts +5 -0
  155. package/dist/tools/query-evaluations.test.d.ts.map +1 -0
  156. package/dist/tools/query-evaluations.test.js +733 -0
  157. package/dist/tools/query-evaluations.test.js.map +1 -0
  158. package/dist/tools/query-llm-events.d.ts +24 -18
  159. package/dist/tools/query-llm-events.d.ts.map +1 -1
  160. package/dist/tools/query-llm-events.js +103 -60
  161. package/dist/tools/query-llm-events.js.map +1 -1
  162. package/dist/tools/query-llm-events.test.js +271 -9
  163. package/dist/tools/query-llm-events.test.js.map +1 -1
  164. package/dist/tools/query-logs.d.ts +28 -20
  165. package/dist/tools/query-logs.d.ts.map +1 -1
  166. package/dist/tools/query-logs.js +85 -61
  167. package/dist/tools/query-logs.js.map +1 -1
  168. package/dist/tools/query-logs.test.js +74 -145
  169. package/dist/tools/query-logs.test.js.map +1 -1
  170. package/dist/tools/query-metrics.d.ts +20 -20
  171. package/dist/tools/query-metrics.d.ts.map +1 -1
  172. package/dist/tools/query-metrics.js +109 -61
  173. package/dist/tools/query-metrics.js.map +1 -1
  174. package/dist/tools/query-metrics.test.js +26 -61
  175. package/dist/tools/query-metrics.test.js.map +1 -1
  176. package/dist/tools/query-traces.d.ts +24 -22
  177. package/dist/tools/query-traces.d.ts.map +1 -1
  178. package/dist/tools/query-traces.js +95 -70
  179. package/dist/tools/query-traces.js.map +1 -1
  180. package/dist/tools/query-traces.test.js +294 -90
  181. package/dist/tools/query-traces.test.js.map +1 -1
  182. package/dist/tools/setup-claudeignore.js +7 -7
  183. package/dist/tools/setup-claudeignore.js.map +1 -1
  184. package/dist/tools/setup-claudeignore.test.js +4 -25
  185. package/dist/tools/setup-claudeignore.test.js.map +1 -1
  186. package/package.json +3 -4
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as path from 'path';
5
5
  import * as os from 'os';
6
6
  import { gzipSync, gunzipSync } from 'zlib';
7
- import { expandTilde, loadJson, loadJsonSafe, getDateString, listFiles, parseDateFromFilename, readJsonlSync, readJsonlSyncWithStats, streamJsonl, filterByDateRange, paginateResults, hasReachedLimit, cleanupOldFiles, isGzipFile, createJsonlStream, BatchWriter, } from './file-utils.js';
7
+ import { expandTilde, loadJson, loadJsonSafe, getDateString, listFiles, parseDateFromFilename, readJsonlSync, readJsonlSyncWithStats, streamJsonl, filterByDateRange, paginateResults, hasReachedLimit, cleanupOldFiles, isGzipFile, createJsonlStream, BatchWriter, StreamingAggregator, enforceMemoryLimit, shouldUseStreamingAggregation, getMemoryLimits, hasCircularReference, estimateByteSize, CONSERVATIVE_FALLBACK_SIZE, } from './file-utils.js';
8
8
  describe('file-utils', () => {
9
9
  let testDir;
10
10
  beforeEach(() => {
@@ -211,9 +211,41 @@ describe('file-utils', () => {
211
211
  const result = parseDateFromFilename('traces-2026-01-28-backup-2026-01-29.jsonl');
212
212
  assert.strictEqual(result, '2026-01-28');
213
213
  });
214
- it('should handle invalid date numbers', () => {
214
+ it('should return null for invalid date 9999-99-99 (invalid month/day/year)', () => {
215
215
  const result = parseDateFromFilename('file-9999-99-99.jsonl');
216
- assert.strictEqual(result, '9999-99-99');
216
+ assert.strictEqual(result, null);
217
+ });
218
+ it('should return null for invalid month 13', () => {
219
+ const result = parseDateFromFilename('file-2024-13-01.jsonl');
220
+ assert.strictEqual(result, null);
221
+ });
222
+ it('should return null for invalid month 00', () => {
223
+ const result = parseDateFromFilename('file-2024-00-15.jsonl');
224
+ assert.strictEqual(result, null);
225
+ });
226
+ it('should return null for invalid day 00', () => {
227
+ const result = parseDateFromFilename('file-2024-05-00.jsonl');
228
+ assert.strictEqual(result, null);
229
+ });
230
+ it('should return null for invalid day 32', () => {
231
+ const result = parseDateFromFilename('file-2024-05-32.jsonl');
232
+ assert.strictEqual(result, null);
233
+ });
234
+ it('should return null for year before 2000', () => {
235
+ const result = parseDateFromFilename('file-1999-06-15.jsonl');
236
+ assert.strictEqual(result, null);
237
+ });
238
+ it('should return null for year after 2100', () => {
239
+ const result = parseDateFromFilename('file-2101-06-15.jsonl');
240
+ assert.strictEqual(result, null);
241
+ });
242
+ it('should accept year 2000 (boundary)', () => {
243
+ const result = parseDateFromFilename('file-2000-01-01.jsonl');
244
+ assert.strictEqual(result, '2000-01-01');
245
+ });
246
+ it('should accept year 2100 (boundary)', () => {
247
+ const result = parseDateFromFilename('file-2100-12-31.jsonl');
248
+ assert.strictEqual(result, '2100-12-31');
217
249
  });
218
250
  it('should return null for incomplete date patterns', () => {
219
251
  const result = parseDateFromFilename('file-2026-01.jsonl');
@@ -858,5 +890,414 @@ describe('file-utils', () => {
858
890
  assert.strictEqual(fs.existsSync(filePath), false);
859
891
  });
860
892
  });
893
+ describe('StreamingAggregator', () => {
894
+ it('should compute count correctly', () => {
895
+ const aggregator = new StreamingAggregator();
896
+ aggregator.add(1);
897
+ aggregator.add(2);
898
+ aggregator.add(3);
899
+ assert.strictEqual(aggregator.getResult('count'), 3);
900
+ assert.strictEqual(aggregator.getCount(), 3);
901
+ });
902
+ it('should compute sum correctly', () => {
903
+ const aggregator = new StreamingAggregator();
904
+ aggregator.add(10);
905
+ aggregator.add(20);
906
+ aggregator.add(30);
907
+ assert.strictEqual(aggregator.getResult('sum'), 60);
908
+ });
909
+ it('should compute avg correctly', () => {
910
+ const aggregator = new StreamingAggregator();
911
+ aggregator.add(10);
912
+ aggregator.add(20);
913
+ aggregator.add(30);
914
+ assert.strictEqual(aggregator.getResult('avg'), 20);
915
+ });
916
+ it('should compute min correctly', () => {
917
+ const aggregator = new StreamingAggregator();
918
+ aggregator.add(5);
919
+ aggregator.add(2);
920
+ aggregator.add(8);
921
+ assert.strictEqual(aggregator.getResult('min'), 2);
922
+ });
923
+ it('should compute max correctly', () => {
924
+ const aggregator = new StreamingAggregator();
925
+ aggregator.add(5);
926
+ aggregator.add(2);
927
+ aggregator.add(8);
928
+ assert.strictEqual(aggregator.getResult('max'), 8);
929
+ });
930
+ it('should compute p50 (median) correctly', () => {
931
+ const aggregator = new StreamingAggregator();
932
+ // Add values 1-100 for predictable p50
933
+ for (let i = 1; i <= 100; i++) {
934
+ aggregator.add(i);
935
+ }
936
+ const p50 = aggregator.getResult('p50');
937
+ assert.ok(p50 !== undefined);
938
+ // p50 should be close to 50.5 for 1-100
939
+ assert.ok(Math.abs(p50 - 50.5) < 1);
940
+ });
941
+ it('should compute p95 correctly', () => {
942
+ const aggregator = new StreamingAggregator();
943
+ for (let i = 1; i <= 100; i++) {
944
+ aggregator.add(i);
945
+ }
946
+ const p95 = aggregator.getResult('p95');
947
+ assert.ok(p95 !== undefined);
948
+ // p95 should be close to 95.05 for 1-100
949
+ assert.ok(Math.abs(p95 - 95.05) < 1);
950
+ });
951
+ it('should compute p99 correctly', () => {
952
+ const aggregator = new StreamingAggregator();
953
+ for (let i = 1; i <= 100; i++) {
954
+ aggregator.add(i);
955
+ }
956
+ const p99 = aggregator.getResult('p99');
957
+ assert.ok(p99 !== undefined);
958
+ // p99 should be close to 99.01 for 1-100
959
+ assert.ok(Math.abs(p99 - 99.01) < 1);
960
+ });
961
+ it('should return undefined for aggregations on empty aggregator', () => {
962
+ const aggregator = new StreamingAggregator();
963
+ assert.strictEqual(aggregator.getResult('count'), 0);
964
+ assert.strictEqual(aggregator.getResult('sum'), undefined);
965
+ assert.strictEqual(aggregator.getResult('avg'), undefined);
966
+ assert.strictEqual(aggregator.getResult('min'), undefined);
967
+ assert.strictEqual(aggregator.getResult('max'), undefined);
968
+ assert.strictEqual(aggregator.getResult('p50'), undefined);
969
+ });
970
+ it('should handle single value', () => {
971
+ const aggregator = new StreamingAggregator();
972
+ aggregator.add(42);
973
+ assert.strictEqual(aggregator.getResult('count'), 1);
974
+ assert.strictEqual(aggregator.getResult('sum'), 42);
975
+ assert.strictEqual(aggregator.getResult('avg'), 42);
976
+ assert.strictEqual(aggregator.getResult('min'), 42);
977
+ assert.strictEqual(aggregator.getResult('max'), 42);
978
+ assert.strictEqual(aggregator.getResult('p50'), 42);
979
+ });
980
+ it('should handle negative values', () => {
981
+ const aggregator = new StreamingAggregator();
982
+ aggregator.add(-10);
983
+ aggregator.add(-5);
984
+ aggregator.add(0);
985
+ aggregator.add(5);
986
+ aggregator.add(10);
987
+ assert.strictEqual(aggregator.getResult('sum'), 0);
988
+ assert.strictEqual(aggregator.getResult('min'), -10);
989
+ assert.strictEqual(aggregator.getResult('max'), 10);
990
+ assert.strictEqual(aggregator.getResult('avg'), 0);
991
+ });
992
+ it('should skip non-finite values', () => {
993
+ const aggregator = new StreamingAggregator();
994
+ aggregator.add(10);
995
+ aggregator.add(Infinity);
996
+ aggregator.add(-Infinity);
997
+ aggregator.add(NaN);
998
+ aggregator.add(20);
999
+ assert.strictEqual(aggregator.getResult('count'), 2);
1000
+ assert.strictEqual(aggregator.getResult('sum'), 30);
1001
+ assert.strictEqual(aggregator.getResult('avg'), 15);
1002
+ });
1003
+ it('should support addAll for multiple values', () => {
1004
+ const aggregator = new StreamingAggregator();
1005
+ aggregator.addAll([1, 2, 3, 4, 5]);
1006
+ assert.strictEqual(aggregator.getResult('count'), 5);
1007
+ assert.strictEqual(aggregator.getResult('sum'), 15);
1008
+ assert.strictEqual(aggregator.getResult('avg'), 3);
1009
+ });
1010
+ it('should reset correctly', () => {
1011
+ const aggregator = new StreamingAggregator();
1012
+ aggregator.addAll([1, 2, 3, 4, 5]);
1013
+ aggregator.reset();
1014
+ assert.strictEqual(aggregator.getResult('count'), 0);
1015
+ assert.strictEqual(aggregator.getResult('sum'), undefined);
1016
+ assert.strictEqual(aggregator.getCount(), 0);
1017
+ });
1018
+ it('should return all stats via getStats()', () => {
1019
+ const aggregator = new StreamingAggregator();
1020
+ aggregator.addAll([1, 2, 3, 4, 5]);
1021
+ const stats = aggregator.getStats();
1022
+ assert.strictEqual(stats.count, 5);
1023
+ assert.strictEqual(stats.sum, 15);
1024
+ assert.strictEqual(stats.avg, 3);
1025
+ assert.strictEqual(stats.min, 1);
1026
+ assert.strictEqual(stats.max, 5);
1027
+ assert.ok(stats.p50 !== undefined);
1028
+ });
1029
+ it('should handle large datasets with reservoir sampling', () => {
1030
+ const aggregator = new StreamingAggregator();
1031
+ // Add 50000 values - larger than reservoir size
1032
+ for (let i = 1; i <= 50000; i++) {
1033
+ aggregator.add(i);
1034
+ }
1035
+ assert.strictEqual(aggregator.getResult('count'), 50000);
1036
+ assert.strictEqual(aggregator.getResult('sum'), (50000 * 50001) / 2);
1037
+ assert.strictEqual(aggregator.getResult('min'), 1);
1038
+ assert.strictEqual(aggregator.getResult('max'), 50000);
1039
+ // Percentiles should be approximately correct (within 10% error due to sampling)
1040
+ const p50 = aggregator.getResult('p50');
1041
+ assert.ok(p50 !== undefined);
1042
+ assert.ok(Math.abs(p50 - 25000) / 25000 < 0.10);
1043
+ const p95 = aggregator.getResult('p95');
1044
+ assert.ok(p95 !== undefined);
1045
+ assert.ok(Math.abs(p95 - 47500) / 47500 < 0.10);
1046
+ });
1047
+ it('should handle decimal values', () => {
1048
+ const aggregator = new StreamingAggregator();
1049
+ aggregator.add(0.1);
1050
+ aggregator.add(0.2);
1051
+ aggregator.add(0.3);
1052
+ const sum = aggregator.getResult('sum');
1053
+ assert.ok(sum !== undefined);
1054
+ assert.ok(Math.abs(sum - 0.6) < 0.0001);
1055
+ });
1056
+ });
1057
+ describe('enforceMemoryLimit', () => {
1058
+ it('should return results unchanged when under limit', () => {
1059
+ const items = [1, 2, 3, 4, 5];
1060
+ const result = enforceMemoryLimit(items, 10);
1061
+ assert.deepStrictEqual(result.results, items);
1062
+ assert.strictEqual(result.truncated, false);
1063
+ assert.strictEqual(result.originalCount, 5);
1064
+ });
1065
+ it('should truncate results when over limit', () => {
1066
+ const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1067
+ const result = enforceMemoryLimit(items, 5);
1068
+ assert.deepStrictEqual(result.results, [1, 2, 3, 4, 5]);
1069
+ assert.strictEqual(result.truncated, true);
1070
+ assert.strictEqual(result.originalCount, 10);
1071
+ });
1072
+ it('should return exactly at limit without truncation', () => {
1073
+ const items = [1, 2, 3, 4, 5];
1074
+ const result = enforceMemoryLimit(items, 5);
1075
+ assert.deepStrictEqual(result.results, items);
1076
+ assert.strictEqual(result.truncated, false);
1077
+ assert.strictEqual(result.originalCount, 5);
1078
+ });
1079
+ it('should handle empty array', () => {
1080
+ const result = enforceMemoryLimit([], 10);
1081
+ assert.deepStrictEqual(result.results, []);
1082
+ assert.strictEqual(result.truncated, false);
1083
+ assert.strictEqual(result.originalCount, 0);
1084
+ });
1085
+ it('should work with object arrays', () => {
1086
+ const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
1087
+ const result = enforceMemoryLimit(items, 2);
1088
+ assert.deepStrictEqual(result.results, [{ id: 1 }, { id: 2 }]);
1089
+ assert.strictEqual(result.truncated, true);
1090
+ });
1091
+ it('should use default limit when not specified', () => {
1092
+ const items = Array.from({ length: 100 }, (_, i) => i);
1093
+ const result = enforceMemoryLimit(items);
1094
+ // Default is 10000, so 100 items should not be truncated
1095
+ assert.strictEqual(result.truncated, false);
1096
+ assert.strictEqual(result.results.length, 100);
1097
+ });
1098
+ it('should log warning when truncating (verify via side effect)', () => {
1099
+ // Store original console.warn
1100
+ const originalWarn = console.warn;
1101
+ let warnCalled = false;
1102
+ let warnMessage = '';
1103
+ console.warn = (msg) => {
1104
+ warnCalled = true;
1105
+ warnMessage = msg;
1106
+ };
1107
+ try {
1108
+ const items = [1, 2, 3, 4, 5];
1109
+ enforceMemoryLimit(items, 3);
1110
+ assert.strictEqual(warnCalled, true);
1111
+ assert.ok(warnMessage.includes('[MEMORY]'));
1112
+ assert.ok(warnMessage.includes('5'));
1113
+ assert.ok(warnMessage.includes('3'));
1114
+ }
1115
+ finally {
1116
+ console.warn = originalWarn;
1117
+ }
1118
+ });
1119
+ });
1120
+ describe('shouldUseStreamingAggregation', () => {
1121
+ it('should return false when count is below threshold', () => {
1122
+ assert.strictEqual(shouldUseStreamingAggregation(100, 5000), false);
1123
+ assert.strictEqual(shouldUseStreamingAggregation(4999, 5000), false);
1124
+ assert.strictEqual(shouldUseStreamingAggregation(5000, 5000), false);
1125
+ });
1126
+ it('should return true when count exceeds threshold', () => {
1127
+ assert.strictEqual(shouldUseStreamingAggregation(5001, 5000), true);
1128
+ assert.strictEqual(shouldUseStreamingAggregation(10000, 5000), true);
1129
+ });
1130
+ it('should use default threshold when not specified', () => {
1131
+ // Default is 5000
1132
+ assert.strictEqual(shouldUseStreamingAggregation(4000), false);
1133
+ });
1134
+ it('should work with custom threshold', () => {
1135
+ assert.strictEqual(shouldUseStreamingAggregation(500, 100), true);
1136
+ assert.strictEqual(shouldUseStreamingAggregation(50, 100), false);
1137
+ });
1138
+ });
1139
+ describe('getMemoryLimits', () => {
1140
+ it('should return memory limit constants', () => {
1141
+ const limits = getMemoryLimits();
1142
+ assert.ok('maxResults' in limits);
1143
+ assert.ok('streamingThreshold' in limits);
1144
+ assert.strictEqual(typeof limits.maxResults, 'number');
1145
+ assert.strictEqual(typeof limits.streamingThreshold, 'number');
1146
+ assert.ok(limits.maxResults > 0);
1147
+ assert.ok(limits.streamingThreshold > 0);
1148
+ });
1149
+ });
1150
+ describe('hasCircularReference', () => {
1151
+ it('should return false for primitive values', () => {
1152
+ assert.strictEqual(hasCircularReference(null), false);
1153
+ assert.strictEqual(hasCircularReference(undefined), false);
1154
+ assert.strictEqual(hasCircularReference(42), false);
1155
+ assert.strictEqual(hasCircularReference('string'), false);
1156
+ assert.strictEqual(hasCircularReference(true), false);
1157
+ });
1158
+ it('should return false for simple objects', () => {
1159
+ assert.strictEqual(hasCircularReference({ a: 1, b: 2 }), false);
1160
+ assert.strictEqual(hasCircularReference({ nested: { deep: { value: 1 } } }), false);
1161
+ });
1162
+ it('should return false for simple arrays', () => {
1163
+ assert.strictEqual(hasCircularReference([1, 2, 3]), false);
1164
+ assert.strictEqual(hasCircularReference([{ id: 1 }, { id: 2 }]), false);
1165
+ });
1166
+ it('should detect self-referencing object', () => {
1167
+ const obj = { a: 1 };
1168
+ obj.self = obj;
1169
+ assert.strictEqual(hasCircularReference(obj), true);
1170
+ });
1171
+ it('should detect self-referencing array', () => {
1172
+ const arr = [1, 2, 3];
1173
+ arr.push(arr);
1174
+ assert.strictEqual(hasCircularReference(arr), true);
1175
+ });
1176
+ it('should detect nested circular reference', () => {
1177
+ const obj = { a: { b: { c: {} } } };
1178
+ obj.a.b = obj;
1179
+ assert.strictEqual(hasCircularReference(obj), true);
1180
+ });
1181
+ it('should detect circular reference in array of objects', () => {
1182
+ const obj1 = { id: 1 };
1183
+ const obj2 = { id: 2, ref: obj1 };
1184
+ obj1.ref = obj2;
1185
+ assert.strictEqual(hasCircularReference([obj1, obj2]), true);
1186
+ });
1187
+ it('should handle deeply nested non-circular structures', () => {
1188
+ const deep = { a: { b: { c: { d: { e: { f: 1 } } } } } };
1189
+ assert.strictEqual(hasCircularReference(deep), false);
1190
+ });
1191
+ });
1192
+ describe('estimateByteSize', () => {
1193
+ it('should return 0 for empty array', () => {
1194
+ assert.strictEqual(estimateByteSize([]), 0);
1195
+ });
1196
+ it('should return correct size for small arrays', () => {
1197
+ const items = [{ id: 1 }, { id: 2 }];
1198
+ const expected = JSON.stringify(items).length;
1199
+ assert.strictEqual(estimateByteSize(items), expected);
1200
+ });
1201
+ it('should return conservative estimate for circular references in small array', () => {
1202
+ const obj = { id: 1 };
1203
+ obj.self = obj;
1204
+ const result = estimateByteSize([obj]);
1205
+ assert.strictEqual(result, CONSERVATIVE_FALLBACK_SIZE);
1206
+ });
1207
+ it('should return conservative estimate for circular references in large array', () => {
1208
+ // Create array larger than sample size (100)
1209
+ const circularObj = { id: 1 };
1210
+ circularObj.self = circularObj;
1211
+ const items = [];
1212
+ for (let i = 0; i < 200; i++) {
1213
+ items.push(circularObj);
1214
+ }
1215
+ const result = estimateByteSize(items);
1216
+ assert.strictEqual(result, CONSERVATIVE_FALLBACK_SIZE);
1217
+ });
1218
+ it('should handle mixed circular and non-circular items in large array', () => {
1219
+ // Create array with some circular and some non-circular items
1220
+ const circularObj = { id: 1 };
1221
+ circularObj.self = circularObj;
1222
+ const items = [];
1223
+ // Add 150 normal items (75% of 200)
1224
+ for (let i = 0; i < 150; i++) {
1225
+ items.push({ id: i, data: 'normal' });
1226
+ }
1227
+ // Add 50 circular items (25% of 200)
1228
+ for (let i = 0; i < 50; i++) {
1229
+ items.push(circularObj);
1230
+ }
1231
+ // Since less than 50% failed, should extrapolate from successful samples
1232
+ const result = estimateByteSize(items);
1233
+ assert.ok(result > 0);
1234
+ // Should not return conservative fallback since <50% failed
1235
+ assert.notStrictEqual(result, CONSERVATIVE_FALLBACK_SIZE);
1236
+ });
1237
+ it('should return conservative estimate when >50% of samples fail', () => {
1238
+ // Create array where most items are circular
1239
+ const circularObj = { id: 1 };
1240
+ circularObj.self = circularObj;
1241
+ const items = [];
1242
+ // Add 40 normal items (20% of 200)
1243
+ for (let i = 0; i < 40; i++) {
1244
+ items.push({ id: i });
1245
+ }
1246
+ // Add 160 circular items (80% of 200)
1247
+ for (let i = 0; i < 160; i++) {
1248
+ items.push(circularObj);
1249
+ }
1250
+ const result = estimateByteSize(items);
1251
+ // Should return conservative fallback since >50% failed
1252
+ assert.strictEqual(result, CONSERVATIVE_FALLBACK_SIZE);
1253
+ });
1254
+ it('should extrapolate size for large non-circular arrays', () => {
1255
+ // Create array larger than sample size
1256
+ const items = Array.from({ length: 200 }, (_, i) => ({ id: i, name: `item-${i}` }));
1257
+ const result = estimateByteSize(items);
1258
+ // Result should be positive and reasonable
1259
+ assert.ok(result > 0);
1260
+ // Rough estimate: each item is ~25 bytes, so 200 items ~5000 bytes
1261
+ assert.ok(result > 1000);
1262
+ assert.ok(result < 100000);
1263
+ });
1264
+ });
1265
+ describe('enforceMemoryLimit with circular references', () => {
1266
+ it('should trigger size-based truncation for circular references', () => {
1267
+ // Create items with circular references
1268
+ const obj = { id: 1, data: 'test' };
1269
+ obj.self = obj;
1270
+ // The circular reference should cause estimateByteSize to return 1MB
1271
+ // which exceeds the 50MB limit would not trigger, but the detection
1272
+ // prevents bypass (the conservative estimate ensures the check works)
1273
+ const items = [obj];
1274
+ const result = enforceMemoryLimit(items, 10000);
1275
+ // Since a single item with circular ref returns 1MB estimate,
1276
+ // and 1MB < 50MB limit, it won't truncate
1277
+ // But the important thing is it doesn't crash and returns valid result
1278
+ assert.ok(Array.isArray(result.results));
1279
+ assert.strictEqual(result.originalCount, 1);
1280
+ });
1281
+ it('should handle array with nested circular references without crashing', () => {
1282
+ const obj1 = { id: 1 };
1283
+ const obj2 = { id: 2 };
1284
+ obj1.ref = obj2;
1285
+ obj2.ref = obj1;
1286
+ const items = [obj1, obj2, { normal: 'object' }];
1287
+ const result = enforceMemoryLimit(items, 10000);
1288
+ assert.ok(Array.isArray(result.results));
1289
+ assert.strictEqual(result.originalCount, 3);
1290
+ });
1291
+ it('should not crash when JSON.stringify would throw', () => {
1292
+ // Create an object that would cause JSON.stringify to throw
1293
+ const obj = {};
1294
+ obj.circular = obj;
1295
+ const items = Array.from({ length: 50 }, () => obj);
1296
+ const result = enforceMemoryLimit(items, 10000);
1297
+ // Should not throw and should return valid result
1298
+ assert.ok(Array.isArray(result.results));
1299
+ assert.strictEqual(result.originalCount, 50);
1300
+ });
1301
+ });
861
1302
  });
862
1303
  //# sourceMappingURL=file-utils.test.js.map