@emnudge/wat-fft 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/README.md +230 -0
- package/dist/combined_fast.wasm +0 -0
- package/dist/combined_real.wasm +0 -0
- package/dist/combined_real_f32.wasm +0 -0
- package/dist/combined_real_opt.wasm +0 -0
- package/dist/combined_stockham.wasm +0 -0
- package/dist/combined_stockham_f32.wasm +0 -0
- package/dist/fft_combined.wasm +0 -0
- package/dist/fft_fast_composed.wasm +0 -0
- package/dist/fft_radix4.wasm +0 -0
- package/dist/fft_real_combined.wasm +0 -0
- package/dist/fft_real_combined_fma.wasm +0 -0
- package/dist/fft_real_f32_dual.wasm +0 -0
- package/dist/fft_real_radix4.wasm +0 -0
- package/dist/fft_recursive.wasm +0 -0
- package/dist/fft_stockham_composed.wasm +0 -0
- package/dist/fft_stockham_f32_dual.wasm +0 -0
- package/dist/reverse_bits.wasm +0 -0
- package/dist/swap.wasm +0 -0
- package/index.js +80 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# wat-fft
|
|
2
|
+
|
|
3
|
+
A high-performance FFT implementation in WebAssembly Text format that **significantly outperforms popular JavaScript FFT libraries**.
|
|
4
|
+
|
|
5
|
+
## Performance
|
|
6
|
+
|
|
7
|
+
### Complex FFT
|
|
8
|
+
|
|
9
|
+
Benchmarked against [fft.js](https://github.com/indutny/fft.js) (fastest pure-JS FFT):
|
|
10
|
+
|
|
11
|
+
| Size | wat-fft (f64) | fft.js | Speedup |
|
|
12
|
+
| ------ | ------------------- | --------------- | -------- |
|
|
13
|
+
| N=64 | **3,830,000 ops/s** | 2,794,000 ops/s | **1.4x** |
|
|
14
|
+
| N=128 | **1,586,000 ops/s** | 1,105,000 ops/s | **1.4x** |
|
|
15
|
+
| N=256 | **973,000 ops/s** | 559,000 ops/s | **1.7x** |
|
|
16
|
+
| N=512 | **344,000 ops/s** | 223,000 ops/s | **1.5x** |
|
|
17
|
+
| N=1024 | **191,000 ops/s** | 113,000 ops/s | **1.7x** |
|
|
18
|
+
| N=2048 | **74,500 ops/s** | 47,200 ops/s | **1.6x** |
|
|
19
|
+
| N=4096 | **44,400 ops/s** | 23,400 ops/s | **1.9x** |
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
---
|
|
23
|
+
config:
|
|
24
|
+
xyChart:
|
|
25
|
+
width: 700
|
|
26
|
+
height: 400
|
|
27
|
+
themeVariables:
|
|
28
|
+
xyChart:
|
|
29
|
+
plotColorPalette: "#4ade80, #60a5fa, #a855f7, #f87171"
|
|
30
|
+
---
|
|
31
|
+
xychart-beta
|
|
32
|
+
title "Complex FFT Performance (Million ops/s)"
|
|
33
|
+
x-axis [N=64, N=128, N=256, N=512, N=1024, N=2048, N=4096]
|
|
34
|
+
y-axis "Million ops/s" 0 --> 5
|
|
35
|
+
line [3.83, 1.59, 0.97, 0.34, 0.19, 0.074, 0.044]
|
|
36
|
+
line [4.60, 2.40, 1.17, 0.54, 0.27, 0.124, 0.062]
|
|
37
|
+
line [2.79, 1.11, 0.56, 0.22, 0.11, 0.047, 0.023]
|
|
38
|
+
line [1.90, 0.80, 0.45, 0.18, 0.10, 0.041, 0.022]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> 馃煝 **wat-fft f64** 路 馃數 **wat-fft f32** 路 馃煟 **fft.js** 路 馃敶 **kissfft-js**
|
|
42
|
+
|
|
43
|
+
**Choose f64** (`fft_combined.wasm`) for double precision - **1.4-1.9x faster** than fft.js at all sizes. **Choose f32** (`fft_stockham_f32_dual.wasm`) for maximum speed with single precision - up to **2.6x faster** than fft.js.
|
|
44
|
+
|
|
45
|
+
### Real FFT
|
|
46
|
+
|
|
47
|
+
Benchmarked against [fftw-js](https://www.npmjs.com/package/fftw-js) (Emscripten port of FFTW):
|
|
48
|
+
|
|
49
|
+
| Size | wat-fft (f32) | fftw-js (f32) | Comparison |
|
|
50
|
+
| ------ | ------------------- | --------------- | ---------- |
|
|
51
|
+
| N=64 | **6,700,000 ops/s** | 6,620,000 ops/s | **+1%** |
|
|
52
|
+
| N=128 | **4,290,000 ops/s** | 4,100,000 ops/s | **+5%** |
|
|
53
|
+
| N=256 | **2,170,000 ops/s** | 1,430,000 ops/s | **+51%** |
|
|
54
|
+
| N=512 | **1,130,000 ops/s** | 870,000 ops/s | **+31%** |
|
|
55
|
+
| N=1024 | **525,000 ops/s** | 444,000 ops/s | **+18%** |
|
|
56
|
+
| N=2048 | **264,000 ops/s** | 225,000 ops/s | **+18%** |
|
|
57
|
+
| N=4096 | **116,000 ops/s** | 105,000 ops/s | **+11%** |
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
---
|
|
61
|
+
config:
|
|
62
|
+
xyChart:
|
|
63
|
+
width: 700
|
|
64
|
+
height: 400
|
|
65
|
+
themeVariables:
|
|
66
|
+
xyChart:
|
|
67
|
+
plotColorPalette: "#4ade80, #60a5fa, #f87171, #a855f7"
|
|
68
|
+
---
|
|
69
|
+
xychart-beta
|
|
70
|
+
title "Real FFT Performance (Million ops/s)"
|
|
71
|
+
x-axis [N=64, N=128, N=256, N=512, N=1024, N=2048, N=4096]
|
|
72
|
+
y-axis "Million ops/s" 0 --> 8
|
|
73
|
+
line [4.80, 2.99, 1.28, 0.76, 0.27, 0.16, 0.062]
|
|
74
|
+
line [6.70, 4.29, 2.17, 1.13, 0.525, 0.264, 0.116]
|
|
75
|
+
line [6.62, 4.10, 1.43, 0.87, 0.444, 0.225, 0.105]
|
|
76
|
+
line [2.93, 1.74, 0.75, 0.42, 0.17, 0.094, 0.039]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> 馃煝 **wat-fft f64** 路 馃數 **wat-fft f32** 路 馃敶 **fftw-js** 路 馃煟 **kissfft-js**
|
|
80
|
+
|
|
81
|
+
**wat-fft f32 beats fftw-js at all sizes** (+1% to +51%). **Choose f64** (`fft_real_combined.wasm`) for double precision. **Choose f32** (`fft_real_f32_dual.wasm`) for maximum single-precision speed.
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Install dependencies
|
|
87
|
+
npm install
|
|
88
|
+
|
|
89
|
+
# Build WASM modules
|
|
90
|
+
npm run build
|
|
91
|
+
|
|
92
|
+
# Run tests
|
|
93
|
+
npm test
|
|
94
|
+
|
|
95
|
+
# Run benchmarks
|
|
96
|
+
npm run bench
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Prerequisites
|
|
100
|
+
|
|
101
|
+
- Node.js v18+
|
|
102
|
+
- [wasm-tools](https://github.com/bytecodealliance/wasm-tools)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cargo install wasm-tools
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Usage
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
import fs from "fs";
|
|
112
|
+
|
|
113
|
+
// Load the WASM module (Stockham is recommended for best performance)
|
|
114
|
+
// No JavaScript imports needed - trig functions are computed inline
|
|
115
|
+
const wasmBuffer = fs.readFileSync("dist/combined_stockham.wasm");
|
|
116
|
+
const wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
117
|
+
const instance = await WebAssembly.instantiate(wasmModule);
|
|
118
|
+
const fft = instance.exports;
|
|
119
|
+
|
|
120
|
+
// Prepare input (interleaved complex: [re0, im0, re1, im1, ...])
|
|
121
|
+
const N = 1024;
|
|
122
|
+
const data = new Float64Array(fft.memory.buffer, 0, N * 2);
|
|
123
|
+
for (let i = 0; i < N; i++) {
|
|
124
|
+
data[i * 2] = Math.sin((2 * Math.PI * i) / N); // real
|
|
125
|
+
data[i * 2 + 1] = 0; // imaginary
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Compute FFT
|
|
129
|
+
fft.precompute_twiddles(N);
|
|
130
|
+
fft.fft_stockham(N);
|
|
131
|
+
|
|
132
|
+
// Results are in-place in data[]
|
|
133
|
+
console.log("DC component:", data[0], data[1]);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Implementations
|
|
137
|
+
|
|
138
|
+
**Recommended modules:**
|
|
139
|
+
|
|
140
|
+
| Module | Use Case | Precision |
|
|
141
|
+
| ---------------------------- | ---------------------- | --------- |
|
|
142
|
+
| `fft_combined.wasm` | Complex FFT (any size) | f64 |
|
|
143
|
+
| `fft_real_combined.wasm` | Real FFT (any size) | f64 |
|
|
144
|
+
| `fft_stockham_f32_dual.wasm` | Complex FFT (fastest) | f32 |
|
|
145
|
+
| `fft_real_f32_dual.wasm` | Real FFT (fastest) | f32 |
|
|
146
|
+
|
|
147
|
+
See [docs/IMPLEMENTATIONS.md](docs/IMPLEMENTATIONS.md) for detailed documentation of all modules, usage examples, and numerical accuracy information.
|
|
148
|
+
|
|
149
|
+
## How It Works
|
|
150
|
+
|
|
151
|
+
See [docs/HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md) for algorithm details including:
|
|
152
|
+
|
|
153
|
+
- Real FFT algorithm (N-point real using N/2-point complex)
|
|
154
|
+
- Memory layout and buffer organization
|
|
155
|
+
- SIMD complex multiply implementation
|
|
156
|
+
- Stockham and Radix-4 FFT algorithms
|
|
157
|
+
- Taylor series trigonometry
|
|
158
|
+
|
|
159
|
+
## Scripts
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm run build # Build all WASM modules
|
|
163
|
+
npm test # Run all tests
|
|
164
|
+
npm run bench # Run complex FFT benchmarks
|
|
165
|
+
npm run bench:rfft # Run real FFT benchmarks
|
|
166
|
+
npm run bench:rfft32 # Run f32 real FFT benchmarks
|
|
167
|
+
npm run test:fft # Run comprehensive FFT tests
|
|
168
|
+
npm run test:rfft # Run real FFT tests
|
|
169
|
+
npm run test:permutation # Test permutation algorithms
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Development Tools
|
|
173
|
+
|
|
174
|
+
| Documentation | Description |
|
|
175
|
+
| ------------------------------------------------------ | ---------------------------------------- |
|
|
176
|
+
| [benchmarks/README.md](benchmarks/README.md) | Performance benchmarks and profiling |
|
|
177
|
+
| [tools/README.md](tools/README.md) | Debug tools for FFT development |
|
|
178
|
+
| [docs/OPTIMIZATION_PLAN.md](docs/OPTIMIZATION_PLAN.md) | Optimization strategy and experiment log |
|
|
179
|
+
|
|
180
|
+
## Playground
|
|
181
|
+
|
|
182
|
+
An interactive browser-based playground is available for testing FFT performance with real-world tasks like spectrogram generation.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
cd playground
|
|
186
|
+
npm install
|
|
187
|
+
npm run dev
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Features:
|
|
191
|
+
|
|
192
|
+
- **Multiple FFT implementations**: Compare performance of different wat-fft modules
|
|
193
|
+
- **Audio sources**: Generate synthetic sine wave combinations using Web Audio API's OfflineAudioContext, or load your own audio files
|
|
194
|
+
- **Spectrogram visualization**: Real-time spectrogram rendering with configurable FFT size, hop size, and color scales
|
|
195
|
+
- **Spectrum analyzer**: Live microphone input with bar, curve, and mirrored visualization modes
|
|
196
|
+
- **Performance metrics**: Track FFT execution time and throughput
|
|
197
|
+
|
|
198
|
+
Add your own sample audio files to `playground/public/samples/`.
|
|
199
|
+
|
|
200
|
+
## Testing FFT Implementations
|
|
201
|
+
|
|
202
|
+
The comprehensive FFT test suite (`tests/fft.test.js`) tests all implementations against a reference DFT with various input sizes and patterns.
|
|
203
|
+
|
|
204
|
+
### Run all FFT tests
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npm run test:fft
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Test a single implementation (useful for debugging)
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
node tests/fft.test.js --impl stockham 64 random
|
|
214
|
+
node tests/fft.test.js --impl fast 256 impulse
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Input patterns
|
|
218
|
+
|
|
219
|
+
- `impulse` - Single 1.0 at index 0
|
|
220
|
+
- `constant` - All 1.0 values
|
|
221
|
+
- `singleFreq` - Single cosine wave
|
|
222
|
+
- `random` - Seeded pseudorandom values
|
|
223
|
+
|
|
224
|
+
### Test sizes
|
|
225
|
+
|
|
226
|
+
Powers of 2: 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
ISC
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/swap.wasm
ADDED
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const distDir = join(__dirname, "dist");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load and instantiate a WASM module
|
|
10
|
+
* @param {string} filename - Name of the wasm file in dist/
|
|
11
|
+
* @returns {Promise<WebAssembly.Instance>}
|
|
12
|
+
*/
|
|
13
|
+
async function loadWasm(filename) {
|
|
14
|
+
const wasmPath = join(distDir, filename);
|
|
15
|
+
const wasmBuffer = await readFile(wasmPath);
|
|
16
|
+
const wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
17
|
+
return WebAssembly.instantiate(wasmModule);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Complex FFT with f64 precision (1.4-1.9x faster than fft.js)
|
|
22
|
+
*
|
|
23
|
+
* Exports:
|
|
24
|
+
* - memory: WebAssembly.Memory
|
|
25
|
+
* - precompute_twiddles(n: i32): void
|
|
26
|
+
* - fft_stockham(n: i32): void
|
|
27
|
+
* - ifft_stockham(n: i32): void
|
|
28
|
+
*
|
|
29
|
+
* Input: interleaved complex [re0, im0, re1, im1, ...] as Float64Array
|
|
30
|
+
*/
|
|
31
|
+
export async function createFFT() {
|
|
32
|
+
return loadWasm("fft_combined.wasm");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Complex FFT with f32 precision (up to 2.6x faster than fft.js)
|
|
37
|
+
*
|
|
38
|
+
* Exports:
|
|
39
|
+
* - memory: WebAssembly.Memory
|
|
40
|
+
* - precompute_twiddles(n: i32): void
|
|
41
|
+
* - fft(n: i32): void
|
|
42
|
+
* - ifft(n: i32): void
|
|
43
|
+
*
|
|
44
|
+
* Input: interleaved complex [re0, im0, re1, im1, ...] as Float32Array
|
|
45
|
+
*/
|
|
46
|
+
export async function createFFTf32() {
|
|
47
|
+
return loadWasm("fft_stockham_f32_dual.wasm");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Real FFT with f64 precision
|
|
52
|
+
*
|
|
53
|
+
* Exports:
|
|
54
|
+
* - memory: WebAssembly.Memory
|
|
55
|
+
* - precompute_twiddles(n: i32): void
|
|
56
|
+
* - rfft(n: i32): void
|
|
57
|
+
* - irfft(n: i32): void
|
|
58
|
+
*
|
|
59
|
+
* Input: real values as Float64Array of length N
|
|
60
|
+
* Output: N/2+1 complex bins (interleaved) for positive frequencies
|
|
61
|
+
*/
|
|
62
|
+
export async function createRFFT() {
|
|
63
|
+
return loadWasm("fft_real_combined.wasm");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Real FFT with f32 precision (beats fftw-js at all sizes)
|
|
68
|
+
*
|
|
69
|
+
* Exports:
|
|
70
|
+
* - memory: WebAssembly.Memory
|
|
71
|
+
* - precompute_twiddles(n: i32): void
|
|
72
|
+
* - rfft(n: i32): void
|
|
73
|
+
* - irfft(n: i32): void
|
|
74
|
+
*
|
|
75
|
+
* Input: real values as Float32Array of length N
|
|
76
|
+
* Output: N/2+1 complex bins (interleaved) for positive frequencies
|
|
77
|
+
*/
|
|
78
|
+
export async function createRFFTf32() {
|
|
79
|
+
return loadWasm("fft_real_f32_dual.wasm");
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@emnudge/wat-fft",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "High-performance FFT in WebAssembly",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "EmNudge",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/emnudge/wat-fft"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"fft",
|
|
13
|
+
"rfft",
|
|
14
|
+
"fourier",
|
|
15
|
+
"transform",
|
|
16
|
+
"wasm",
|
|
17
|
+
"webassembly",
|
|
18
|
+
"dsp",
|
|
19
|
+
"signal-processing",
|
|
20
|
+
"audio"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"dist/*.wasm"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "npm run test:swap && npm run test:reverse_bits && npm run test:fft",
|
|
37
|
+
"test:all": "npm run test && npm run test:rfft && npm run test:property && npm run test:boundary && npm run test:correctness && npm run test:output-order",
|
|
38
|
+
"test:output-order": "node --test tests/output-order.test.js",
|
|
39
|
+
"test:swap": "node tests/swap.test.js",
|
|
40
|
+
"test:reverse_bits": "node tests/reverse_bits.test.js",
|
|
41
|
+
"test:permutation": "node tests/permutation.test.js",
|
|
42
|
+
"test:fft": "node tests/fft.test.js",
|
|
43
|
+
"test:rfft": "node --test tests/rfft.test.js",
|
|
44
|
+
"test:f32": "node tests/fft_f32_dual.test.js",
|
|
45
|
+
"test:property": "node tests/fft.property.test.js",
|
|
46
|
+
"test:boundary": "node --test tests/boundary.test.js",
|
|
47
|
+
"test:butterfly": "node tools/butterfly_tester.js",
|
|
48
|
+
"test:correctness": "node --test tests/correctness/*.test.js",
|
|
49
|
+
"test:correctness:roundtrip": "node --test tests/correctness/fft.roundtrip.test.js",
|
|
50
|
+
"test:correctness:parseval": "node --test tests/correctness/fft.parseval.test.js",
|
|
51
|
+
"test:correctness:linearity": "node --test tests/correctness/fft.linearity.test.js",
|
|
52
|
+
"test:correctness:shift": "node --test tests/correctness/fft.shift.test.js",
|
|
53
|
+
"test:correctness:reference": "node --test tests/correctness/fft.reference.test.js",
|
|
54
|
+
"test:correctness:known": "node --test tests/correctness/fft.known-values.test.js",
|
|
55
|
+
"debug:stockham": "node tools/wasm_compare.js",
|
|
56
|
+
"debug:index": "node tools/index_visualizer.js",
|
|
57
|
+
"debug:perm": "node tools/permutation_validator.js",
|
|
58
|
+
"debug:ref": "node tools/stockham_reference.js",
|
|
59
|
+
"build": "node build.js",
|
|
60
|
+
"bench": "node benchmarks/fft.bench.js",
|
|
61
|
+
"bench:rfft": "node benchmarks/rfft.bench.js",
|
|
62
|
+
"bench:f32": "node benchmarks/fft_f32_dual.bench.js",
|
|
63
|
+
"bench:rfft32": "node benchmarks/rfft_f32_dual.bench.js",
|
|
64
|
+
"lint": "oxlint .",
|
|
65
|
+
"format": "oxfmt --write .",
|
|
66
|
+
"format:check": "oxfmt --check ."
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"fast-check": "^4.5.3",
|
|
70
|
+
"fft-js": "^0.0.12",
|
|
71
|
+
"fft.js": "^4.0.4",
|
|
72
|
+
"fftw-js": "^0.1.4",
|
|
73
|
+
"kissfft-js": "^0.1.8",
|
|
74
|
+
"kissfft-wasm": "^2.0.1",
|
|
75
|
+
"oxfmt": "^0.26.0",
|
|
76
|
+
"oxlint": "^1.41.0"
|
|
77
|
+
}
|
|
78
|
+
}
|