dspx 0.1.1-alpha.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.
Files changed (172) hide show
  1. package/.github/workflows/ci.yml +185 -0
  2. package/.vscode/c_cpp_properties.json +17 -0
  3. package/.vscode/settings.json +68 -0
  4. package/.vscode/tasks.json +28 -0
  5. package/DISCLAIMER.md +32 -0
  6. package/LICENSE +21 -0
  7. package/README.md +1803 -0
  8. package/ROADMAP.md +192 -0
  9. package/TECHNICAL_DEBT.md +165 -0
  10. package/binding.gyp +65 -0
  11. package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
  12. package/docs/AUTHENTICATION_SECURITY.md +396 -0
  13. package/docs/BACKEND_IMPROVEMENTS.md +399 -0
  14. package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
  15. package/docs/FFT_IMPLEMENTATION.md +490 -0
  16. package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
  17. package/docs/FFT_USER_GUIDE.md +494 -0
  18. package/docs/FILTERS_IMPLEMENTATION.md +260 -0
  19. package/docs/FILTER_API_GUIDE.md +418 -0
  20. package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
  21. package/docs/LOGGER_API_REFERENCE.md +350 -0
  22. package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
  23. package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
  24. package/docs/PHASES_5_7_SUMMARY.md +403 -0
  25. package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
  26. package/docs/SIMD_OPTIMIZATIONS.md +211 -0
  27. package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
  28. package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
  29. package/docs/TIMESERIES_QUICK_REF.md +85 -0
  30. package/docs/advanced.md +559 -0
  31. package/docs/time-series-guide.md +617 -0
  32. package/docs/time-series-migration.md +376 -0
  33. package/jest.config.js +37 -0
  34. package/package.json +42 -0
  35. package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
  36. package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
  37. package/scripts/test.js +24 -0
  38. package/src/build/dsp-ts-redis.node +0 -0
  39. package/src/native/DspPipeline.cc +675 -0
  40. package/src/native/DspPipeline.h +44 -0
  41. package/src/native/FftBindings.cc +817 -0
  42. package/src/native/FilterBindings.cc +1001 -0
  43. package/src/native/IDspStage.h +53 -0
  44. package/src/native/adapters/InterpolatorStage.h +201 -0
  45. package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
  46. package/src/native/adapters/MovingAverageStage.h +306 -0
  47. package/src/native/adapters/RectifyStage.h +88 -0
  48. package/src/native/adapters/ResamplerStage.h +238 -0
  49. package/src/native/adapters/RmsStage.h +299 -0
  50. package/src/native/adapters/SscStage.h +121 -0
  51. package/src/native/adapters/VarianceStage.h +307 -0
  52. package/src/native/adapters/WampStage.h +114 -0
  53. package/src/native/adapters/WaveformLengthStage.h +115 -0
  54. package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
  55. package/src/native/core/FftEngine.cc +441 -0
  56. package/src/native/core/FftEngine.h +224 -0
  57. package/src/native/core/FirFilter.cc +324 -0
  58. package/src/native/core/FirFilter.h +149 -0
  59. package/src/native/core/IirFilter.cc +576 -0
  60. package/src/native/core/IirFilter.h +210 -0
  61. package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
  62. package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
  63. package/src/native/core/MovingAverageFilter.cc +18 -0
  64. package/src/native/core/MovingAverageFilter.h +135 -0
  65. package/src/native/core/MovingFftFilter.cc +291 -0
  66. package/src/native/core/MovingFftFilter.h +203 -0
  67. package/src/native/core/MovingVarianceFilter.cc +194 -0
  68. package/src/native/core/MovingVarianceFilter.h +114 -0
  69. package/src/native/core/MovingZScoreFilter.cc +215 -0
  70. package/src/native/core/MovingZScoreFilter.h +113 -0
  71. package/src/native/core/Policies.h +352 -0
  72. package/src/native/core/RmsFilter.cc +18 -0
  73. package/src/native/core/RmsFilter.h +131 -0
  74. package/src/native/core/SscFilter.cc +16 -0
  75. package/src/native/core/SscFilter.h +137 -0
  76. package/src/native/core/WampFilter.cc +16 -0
  77. package/src/native/core/WampFilter.h +101 -0
  78. package/src/native/core/WaveformLengthFilter.cc +17 -0
  79. package/src/native/core/WaveformLengthFilter.h +98 -0
  80. package/src/native/utils/CircularBufferArray.cc +336 -0
  81. package/src/native/utils/CircularBufferArray.h +62 -0
  82. package/src/native/utils/CircularBufferVector.cc +145 -0
  83. package/src/native/utils/CircularBufferVector.h +45 -0
  84. package/src/native/utils/NapiUtils.cc +53 -0
  85. package/src/native/utils/NapiUtils.h +21 -0
  86. package/src/native/utils/SimdOps.h +870 -0
  87. package/src/native/utils/SlidingWindowFilter.cc +239 -0
  88. package/src/native/utils/SlidingWindowFilter.h +159 -0
  89. package/src/native/utils/TimeSeriesBuffer.cc +205 -0
  90. package/src/native/utils/TimeSeriesBuffer.h +140 -0
  91. package/src/ts/CircularLogBuffer.ts +87 -0
  92. package/src/ts/DriftDetector.ts +331 -0
  93. package/src/ts/TopicRouter.ts +428 -0
  94. package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
  95. package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
  96. package/src/ts/__tests__/Chaining.test.ts +387 -0
  97. package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
  98. package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
  99. package/src/ts/__tests__/DriftDetector.test.ts +389 -0
  100. package/src/ts/__tests__/Fft.test.ts +484 -0
  101. package/src/ts/__tests__/ListState.test.ts +153 -0
  102. package/src/ts/__tests__/Logger.test.ts +208 -0
  103. package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
  104. package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
  105. package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
  106. package/src/ts/__tests__/MovingAverage.test.ts +322 -0
  107. package/src/ts/__tests__/RMS.test.ts +315 -0
  108. package/src/ts/__tests__/Rectify.test.ts +272 -0
  109. package/src/ts/__tests__/Redis.test.ts +456 -0
  110. package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
  111. package/src/ts/__tests__/Tap.test.ts +164 -0
  112. package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
  113. package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
  114. package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
  115. package/src/ts/__tests__/TimeSeries.test.ts +254 -0
  116. package/src/ts/__tests__/TopicRouter.test.ts +332 -0
  117. package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
  118. package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
  119. package/src/ts/__tests__/Variance.test.ts +509 -0
  120. package/src/ts/__tests__/WaveformLength.test.ts +147 -0
  121. package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
  122. package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
  123. package/src/ts/advanced-dsp.ts +566 -0
  124. package/src/ts/backends.ts +1137 -0
  125. package/src/ts/bindings.ts +1225 -0
  126. package/src/ts/easter-egg.ts +42 -0
  127. package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
  128. package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
  129. package/src/ts/examples/MovingAverage/test-state.ts +85 -0
  130. package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
  131. package/src/ts/examples/RMS/test-state.ts +97 -0
  132. package/src/ts/examples/RMS/test-streaming.ts +253 -0
  133. package/src/ts/examples/Rectify/test-state.ts +107 -0
  134. package/src/ts/examples/Rectify/test-streaming.ts +242 -0
  135. package/src/ts/examples/Variance/test-state.ts +195 -0
  136. package/src/ts/examples/Variance/test-streaming.ts +260 -0
  137. package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
  138. package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
  139. package/src/ts/examples/advanced-dsp-examples.ts +397 -0
  140. package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
  141. package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
  142. package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
  143. package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
  144. package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
  145. package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
  146. package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
  147. package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
  148. package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
  149. package/src/ts/examples/chaining/test-chaining.ts +52 -0
  150. package/src/ts/examples/emg-features-example.ts +284 -0
  151. package/src/ts/examples/fft-example.ts +309 -0
  152. package/src/ts/examples/fft-examples.ts +349 -0
  153. package/src/ts/examples/filter-examples.ts +320 -0
  154. package/src/ts/examples/list-state-example.ts +131 -0
  155. package/src/ts/examples/logger-example.ts +91 -0
  156. package/src/ts/examples/notch-filter-examples.ts +243 -0
  157. package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
  158. package/src/ts/examples/phase6-7/production-observability.ts +476 -0
  159. package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
  160. package/src/ts/examples/redis/redis-example.ts +202 -0
  161. package/src/ts/examples/redis-example.ts +202 -0
  162. package/src/ts/examples/simd-benchmark.ts +126 -0
  163. package/src/ts/examples/tap-debugging.ts +230 -0
  164. package/src/ts/examples/timeseries/comparison-example.ts +290 -0
  165. package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
  166. package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
  167. package/src/ts/examples/waveform-length-example.ts +139 -0
  168. package/src/ts/fft.ts +722 -0
  169. package/src/ts/filters.ts +1078 -0
  170. package/src/ts/index.ts +120 -0
  171. package/src/ts/types.ts +589 -0
  172. package/tsconfig.json +15 -0
@@ -0,0 +1,229 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { IirFilter } from "../filters.js";
4
+
5
+ const sampleRate = 44100;
6
+
7
+ describe("Chebyshev Filters", () => {
8
+ test("should create Chebyshev low-pass filter", async () => {
9
+ const filter = IirFilter.createChebyshevLowPass({
10
+ cutoffFrequency: 1000,
11
+ sampleRate,
12
+ order: 2,
13
+ rippleDb: 0.5,
14
+ });
15
+
16
+ assert.strictEqual(filter.getBCoefficients().length, 3);
17
+ assert.strictEqual(filter.getACoefficients().length, 2);
18
+
19
+ // Test filtering
20
+ const testSignal = new Float32Array(10).fill(1.0);
21
+ const filtered = await filter.process(testSignal);
22
+ assert.strictEqual(filtered.length, 10);
23
+ });
24
+
25
+ test("should create Chebyshev high-pass filter", async () => {
26
+ const filter = IirFilter.createChebyshevHighPass({
27
+ cutoffFrequency: 500,
28
+ sampleRate,
29
+ order: 2,
30
+ rippleDb: 0.5,
31
+ });
32
+
33
+ const testSignal = new Float32Array(10).fill(1.0);
34
+ const filtered = await filter.process(testSignal);
35
+ assert.strictEqual(filtered.length, 10);
36
+ });
37
+
38
+ test("should create Chebyshev band-pass filter", async () => {
39
+ const filter = IirFilter.createChebyshevBandPass({
40
+ lowCutoffFrequency: 500,
41
+ highCutoffFrequency: 2000,
42
+ sampleRate,
43
+ order: 2,
44
+ rippleDb: 0.5,
45
+ });
46
+
47
+ assert.strictEqual(filter.getBCoefficients().length, 3);
48
+
49
+ const testSignal = new Float32Array(10).fill(1.0);
50
+ await filter.process(testSignal);
51
+ });
52
+
53
+ test("should use default ripple of 0.5 dB", () => {
54
+ const filter1 = IirFilter.createChebyshevLowPass({
55
+ cutoffFrequency: 1000,
56
+ sampleRate,
57
+ order: 2,
58
+ });
59
+
60
+ const filter2 = IirFilter.createChebyshevLowPass({
61
+ cutoffFrequency: 1000,
62
+ sampleRate,
63
+ order: 2,
64
+ rippleDb: 0.5,
65
+ });
66
+
67
+ const bCoeffs1 = filter1.getBCoefficients();
68
+ const bCoeffs2 = filter2.getBCoefficients();
69
+
70
+ // Coefficients should be equal for same ripple
71
+ assert.strictEqual(bCoeffs1.length, bCoeffs2.length);
72
+ });
73
+
74
+ test("should support different ripple values", () => {
75
+ const filter1 = IirFilter.createChebyshevLowPass({
76
+ cutoffFrequency: 1000,
77
+ sampleRate,
78
+ order: 2,
79
+ rippleDb: 0.1,
80
+ });
81
+
82
+ const filter2 = IirFilter.createChebyshevLowPass({
83
+ cutoffFrequency: 1000,
84
+ sampleRate,
85
+ order: 2,
86
+ rippleDb: 1.0,
87
+ });
88
+
89
+ const bCoeffs1 = filter1.getBCoefficients();
90
+ const bCoeffs2 = filter2.getBCoefficients();
91
+
92
+ // Both should have coefficients
93
+ assert.strictEqual(bCoeffs1.length, 3);
94
+ assert.strictEqual(bCoeffs2.length, 3);
95
+ });
96
+ });
97
+
98
+ describe("Biquad EQ Filters", () => {
99
+ test("should create peaking EQ filter with boost", async () => {
100
+ const filter = IirFilter.createPeakingEQ({
101
+ centerFrequency: 1000,
102
+ sampleRate,
103
+ Q: 2.0,
104
+ gainDb: 6.0,
105
+ });
106
+
107
+ const bCoeffs = filter.getBCoefficients();
108
+ const aCoeffs = filter.getACoefficients();
109
+
110
+ assert.strictEqual(bCoeffs.length, 3);
111
+ assert.strictEqual(aCoeffs.length, 2);
112
+
113
+ // Test basic processing
114
+ const testSignal = new Float32Array(20).fill(1.0);
115
+ const boosted = await filter.process(testSignal);
116
+ assert.strictEqual(boosted.length, 20);
117
+ });
118
+
119
+ test("should create low-shelf filter", async () => {
120
+ const filter = IirFilter.createLowShelf({
121
+ cutoffFrequency: 1000,
122
+ sampleRate,
123
+ gainDb: 6.0,
124
+ Q: 0.707,
125
+ });
126
+
127
+ const testSignal = new Float32Array(10).fill(1.0);
128
+ const processed = await filter.process(testSignal);
129
+
130
+ assert.strictEqual(processed.length, 10);
131
+ });
132
+
133
+ test("should create high-shelf filter with attenuation", async () => {
134
+ const filter = IirFilter.createHighShelf({
135
+ cutoffFrequency: 1000,
136
+ sampleRate,
137
+ gainDb: -6.0,
138
+ Q: 0.707,
139
+ });
140
+
141
+ const testSignal = new Float32Array(20).fill(1.0);
142
+ const attenuated = await filter.process(testSignal);
143
+
144
+ assert.strictEqual(attenuated.length, 20);
145
+ });
146
+
147
+ test("should support EQ chain", async () => {
148
+ // Create 3-band parametric EQ
149
+ const lowShelf = IirFilter.createLowShelf({
150
+ cutoffFrequency: 200,
151
+ sampleRate,
152
+ gainDb: 3.0,
153
+ Q: 0.707,
154
+ });
155
+
156
+ const midPeak = IirFilter.createPeakingEQ({
157
+ centerFrequency: 1000,
158
+ sampleRate,
159
+ Q: 1.5,
160
+ gainDb: -6.0,
161
+ });
162
+
163
+ const highShelf = IirFilter.createHighShelf({
164
+ cutoffFrequency: 3000,
165
+ sampleRate,
166
+ gainDb: 2.0,
167
+ Q: 0.707,
168
+ });
169
+
170
+ // Test signal
171
+ const testSignal = new Float32Array(100).fill(1.0);
172
+
173
+ // Apply EQ chain
174
+ let processed = await lowShelf.process(testSignal);
175
+ processed = await midPeak.process(processed);
176
+ processed = await highShelf.process(processed);
177
+
178
+ assert.strictEqual(processed.length, 100);
179
+ });
180
+
181
+ test("should create peaking EQ with negative gain (cut)", async () => {
182
+ const filter = IirFilter.createPeakingEQ({
183
+ centerFrequency: 1000,
184
+ sampleRate,
185
+ Q: 2.0,
186
+ gainDb: -12.0,
187
+ });
188
+
189
+ const testSignal = new Float32Array(20).fill(1.0);
190
+ const cut = await filter.process(testSignal);
191
+
192
+ assert.strictEqual(cut.length, 20);
193
+ });
194
+ });
195
+
196
+ describe("Filter Validation", () => {
197
+ test("should reject ripple > 3 dB", () => {
198
+ assert.throws(() => {
199
+ IirFilter.createChebyshevLowPass({
200
+ cutoffFrequency: 1000,
201
+ sampleRate,
202
+ order: 2,
203
+ rippleDb: 5.0,
204
+ });
205
+ }, /ripple/i);
206
+ });
207
+
208
+ test("should reject Q = 0 for peaking EQ", () => {
209
+ assert.throws(() => {
210
+ IirFilter.createPeakingEQ({
211
+ centerFrequency: 1000,
212
+ sampleRate,
213
+ Q: 0,
214
+ gainDb: 6.0,
215
+ });
216
+ }, /Q must be positive/i);
217
+ });
218
+
219
+ test("should reject negative Q", () => {
220
+ assert.throws(() => {
221
+ IirFilter.createPeakingEQ({
222
+ centerFrequency: 1000,
223
+ sampleRate,
224
+ Q: -1.5,
225
+ gainDb: 6.0,
226
+ });
227
+ }, /Q must be positive/i);
228
+ });
229
+ });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Unit tests for CircularLogBuffer
3
+ */
4
+
5
+ import { describe, it } from "node:test";
6
+ import assert from "node:assert";
7
+ import { CircularLogBuffer } from "../CircularLogBuffer.js";
8
+ import type { LogEntry } from "../types.js";
9
+
10
+ describe("CircularLogBuffer", () => {
11
+ it("should start empty", () => {
12
+ const buffer = new CircularLogBuffer(4);
13
+ assert.strictEqual(buffer.size(), 0);
14
+ assert.strictEqual(buffer.hasEntries(), false);
15
+ assert.deepStrictEqual(buffer.flush(), []);
16
+ });
17
+
18
+ it("should store and retrieve entries in order", () => {
19
+ const buffer = new CircularLogBuffer(4);
20
+
21
+ const entry1: LogEntry = {
22
+ level: "info",
23
+ message: "First",
24
+ timestamp: 1,
25
+ };
26
+ const entry2: LogEntry = {
27
+ level: "debug",
28
+ message: "Second",
29
+ timestamp: 2,
30
+ };
31
+
32
+ buffer.push(entry1);
33
+ buffer.push(entry2);
34
+
35
+ assert.strictEqual(buffer.size(), 2);
36
+ assert.strictEqual(buffer.hasEntries(), true);
37
+
38
+ const entries = buffer.flush();
39
+ assert.strictEqual(entries.length, 2);
40
+ assert.strictEqual(entries[0].message, "First");
41
+ assert.strictEqual(entries[1].message, "Second");
42
+
43
+ // Should be empty after flush
44
+ assert.strictEqual(buffer.size(), 0);
45
+ assert.strictEqual(buffer.hasEntries(), false);
46
+ });
47
+
48
+ it("should overwrite oldest entries when full", () => {
49
+ const buffer = new CircularLogBuffer(3);
50
+
51
+ buffer.push({ level: "info", message: "1", timestamp: 1 });
52
+ buffer.push({ level: "info", message: "2", timestamp: 2 });
53
+ buffer.push({ level: "info", message: "3", timestamp: 3 });
54
+ buffer.push({ level: "info", message: "4", timestamp: 4 }); // Overwrites "1"
55
+ buffer.push({ level: "info", message: "5", timestamp: 5 }); // Overwrites "2"
56
+
57
+ const entries = buffer.flush();
58
+ assert.strictEqual(entries.length, 3);
59
+ assert.strictEqual(entries[0].message, "3"); // Oldest
60
+ assert.strictEqual(entries[1].message, "4");
61
+ assert.strictEqual(entries[2].message, "5"); // Newest
62
+ });
63
+
64
+ it("should handle wrap-around correctly", () => {
65
+ const buffer = new CircularLogBuffer(4);
66
+
67
+ // Fill buffer
68
+ buffer.push({ level: "info", message: "A", timestamp: 1 });
69
+ buffer.push({ level: "info", message: "B", timestamp: 2 });
70
+ buffer.push({ level: "info", message: "C", timestamp: 3 });
71
+ buffer.push({ level: "info", message: "D", timestamp: 4 });
72
+
73
+ // Flush and refill
74
+ buffer.flush();
75
+
76
+ buffer.push({ level: "info", message: "E", timestamp: 5 });
77
+ buffer.push({ level: "info", message: "F", timestamp: 6 });
78
+
79
+ const entries = buffer.flush();
80
+ assert.strictEqual(entries.length, 2);
81
+ assert.strictEqual(entries[0].message, "E");
82
+ assert.strictEqual(entries[1].message, "F");
83
+ });
84
+
85
+ it("should clear without allocating new memory", () => {
86
+ const buffer = new CircularLogBuffer(4);
87
+
88
+ buffer.push({ level: "info", message: "Test", timestamp: 1 });
89
+ buffer.push({ level: "info", message: "Test", timestamp: 2 });
90
+
91
+ assert.strictEqual(buffer.size(), 2);
92
+
93
+ buffer.clear();
94
+
95
+ assert.strictEqual(buffer.size(), 0);
96
+ assert.strictEqual(buffer.hasEntries(), false);
97
+ assert.deepStrictEqual(buffer.flush(), []);
98
+ });
99
+
100
+ it("should handle context in log entries", () => {
101
+ const buffer = new CircularLogBuffer(4);
102
+
103
+ buffer.push({
104
+ level: "error",
105
+ message: "Error occurred",
106
+ context: { code: 500, path: "/api/test" },
107
+ timestamp: 123,
108
+ });
109
+
110
+ const entries = buffer.flush();
111
+ assert.strictEqual(entries[0].context?.code, 500);
112
+ assert.strictEqual(entries[0].context?.path, "/api/test");
113
+ });
114
+
115
+ it("should maintain chronological order when full", () => {
116
+ const buffer = new CircularLogBuffer(5);
117
+
118
+ // Add 10 entries (buffer capacity is 5)
119
+ for (let i = 1; i <= 10; i++) {
120
+ buffer.push({
121
+ level: "info",
122
+ message: `Entry ${i}`,
123
+ timestamp: i,
124
+ });
125
+ }
126
+
127
+ const entries = buffer.flush();
128
+ assert.strictEqual(entries.length, 5);
129
+
130
+ // Should have entries 6-10 (oldest to newest)
131
+ assert.strictEqual(entries[0].message, "Entry 6");
132
+ assert.strictEqual(entries[1].message, "Entry 7");
133
+ assert.strictEqual(entries[2].message, "Entry 8");
134
+ assert.strictEqual(entries[3].message, "Entry 9");
135
+ assert.strictEqual(entries[4].message, "Entry 10");
136
+ });
137
+
138
+ it("should support multiple flush cycles", () => {
139
+ const buffer = new CircularLogBuffer(3);
140
+
141
+ // Cycle 1
142
+ buffer.push({ level: "info", message: "A", timestamp: 1 });
143
+ buffer.push({ level: "info", message: "B", timestamp: 2 });
144
+ let entries = buffer.flush();
145
+ assert.strictEqual(entries.length, 2);
146
+ assert.strictEqual(entries[0].message, "A");
147
+
148
+ // Cycle 2
149
+ buffer.push({ level: "info", message: "C", timestamp: 3 });
150
+ entries = buffer.flush();
151
+ assert.strictEqual(entries.length, 1);
152
+ assert.strictEqual(entries[0].message, "C");
153
+
154
+ // Cycle 3 (empty)
155
+ entries = buffer.flush();
156
+ assert.strictEqual(entries.length, 0);
157
+ });
158
+ });