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.
- package/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- 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
|
+
});
|