@ff-labs/fff-bun 0.8.5-nightly.ccb1b9d → 0.9.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 +53 -208
- package/examples/glob-bench.ts +159 -0
- package/package.json +9 -9
- package/src/{types.ts → fff-api.ts} +155 -2
- package/src/ffi.ts +125 -42
- package/src/finder.ts +83 -18
- package/src/git-lifecycle.test.ts +2 -2
- package/src/index.test.ts +89 -5
- package/src/index.ts +13 -55
package/README.md
CHANGED
|
@@ -1,257 +1,102 @@
|
|
|
1
1
|
# fff - Fast File Finder
|
|
2
2
|
|
|
3
|
-
High-performance fuzzy file finder for Bun, powered by Rust.
|
|
3
|
+
High-performance fuzzy file finder for Bun, powered by Rust. Extremely fast live file, content, and directory search with a typo-resistant algorithm. As well as regex, plain-text, multi-occurrence and typo-resistant content search.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Comes with built-in git status support, frecency access tracking, and a real-time file watcher, content indexing and many more! Designed for LLM agent tools that search through codebases or agentic RAG document search.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Smart ranking** - Frecency-based scoring (frequency + recency)
|
|
9
|
-
- **Git-aware** - Shows file git status in results
|
|
10
|
-
- **Query history** - Learns from your search patterns
|
|
11
|
-
- **Type-safe** - Full TypeScript support with Result types
|
|
7
|
+
Faster than ripgrep & fzf on any workflow that runs more than once per process.
|
|
12
8
|
|
|
13
9
|
## Installation
|
|
14
10
|
|
|
15
11
|
```bash
|
|
16
|
-
bun add @ff-labs/bun
|
|
12
|
+
bun add @ff-labs/fff-bun
|
|
17
13
|
```
|
|
18
14
|
|
|
19
|
-
The correct native binary for your platform is installed automatically via platform-specific packages (e.g. `@ff-labs/fff-bin-darwin-arm64`, `@ff-labs/fff-bin-linux-x64-gnu`)
|
|
15
|
+
The correct native binary for your platform is installed automatically via platform-specific packages (e.g. `@ff-labs/fff-bin-darwin-arm64`, `@ff-labs/fff-bin-linux-x64-gnu`)
|
|
20
16
|
|
|
21
17
|
### Supported Platforms
|
|
22
18
|
|
|
23
|
-
| Platform | Architecture
|
|
24
|
-
|
|
25
|
-
| macOS
|
|
26
|
-
| macOS
|
|
27
|
-
| Linux
|
|
28
|
-
| Linux
|
|
29
|
-
| Linux
|
|
30
|
-
| Linux
|
|
31
|
-
| Windows
|
|
32
|
-
| Windows
|
|
19
|
+
| Platform | Architecture | Package |
|
|
20
|
+
| -------- | --------------------- | ----------------------------------- |
|
|
21
|
+
| macOS | ARM64 (Apple Silicon) | `@ff-labs/fff-bin-darwin-arm64` |
|
|
22
|
+
| macOS | x64 (Intel) | `@ff-labs/fff-bin-darwin-x64` |
|
|
23
|
+
| Linux | x64 (glibc) | `@ff-labs/fff-bin-linux-x64-gnu` |
|
|
24
|
+
| Linux | ARM64 (glibc) | `@ff-labs/fff-bin-linux-arm64-gnu` |
|
|
25
|
+
| Linux | x64 (musl) | `@ff-labs/fff-bin-linux-x64-musl` |
|
|
26
|
+
| Linux | ARM64 (musl) | `@ff-labs/fff-bin-linux-arm64-musl` |
|
|
27
|
+
| Windows | x64 | `@ff-labs/fff-bin-win32-x64` |
|
|
28
|
+
| Windows | ARM64 | `@ff-labs/fff-bin-win32-arm64` |
|
|
33
29
|
|
|
34
30
|
If the platform package isn't available, the postinstall script will attempt to download from GitHub releases as a fallback.
|
|
35
31
|
|
|
36
32
|
## Quick Start
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Initialize with a directory
|
|
42
|
-
const result = FileFinder.init({ basePath: "/path/to/project" });
|
|
43
|
-
if (!result.ok) {
|
|
44
|
-
console.error(result.error);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Wait for initial scan
|
|
49
|
-
FileFinder.waitForScan(5000);
|
|
50
|
-
|
|
51
|
-
// Search for files
|
|
52
|
-
const search = FileFinder.search("main.ts");
|
|
53
|
-
if (search.ok) {
|
|
54
|
-
for (const item of search.value.items) {
|
|
55
|
-
console.log(item.relativePath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Cleanup when done
|
|
60
|
-
FileFinder.destroy();
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## API Reference
|
|
64
|
-
|
|
65
|
-
### `FileFinder.init(options)`
|
|
66
|
-
|
|
67
|
-
Initialize the file finder.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
interface InitOptions {
|
|
71
|
-
basePath: string; // Directory to index (required)
|
|
72
|
-
frecencyDbPath?: string; // Frecency DB path (omit to skip frecency)
|
|
73
|
-
historyDbPath?: string; // History DB path (omit to skip query tracking)
|
|
74
|
-
useUnsafeNoLock?: boolean; // Faster but less safe DB mode
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const result = FileFinder.init({ basePath: "/my/project" });
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### `FileFinder.search(query, options?)`
|
|
81
|
-
|
|
82
|
-
Search for files.
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
interface SearchOptions {
|
|
86
|
-
maxThreads?: number; // Parallel threads (0 = auto)
|
|
87
|
-
currentFile?: string; // Deprioritize this file
|
|
88
|
-
comboBoostMultiplier?: number; // Query history boost
|
|
89
|
-
minComboCount?: number; // Min history matches
|
|
90
|
-
pageIndex?: number; // Pagination offset
|
|
91
|
-
pageSize?: number; // Results per page
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const result = FileFinder.search("main.ts", { pageSize: 10 });
|
|
95
|
-
if (result.ok) {
|
|
96
|
-
console.log(`Found ${result.value.totalMatched} files`);
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Query Syntax
|
|
101
|
-
|
|
102
|
-
- `foo bar` - Match files containing "foo" and "bar"
|
|
103
|
-
- `src/` - Match files in src directory
|
|
104
|
-
- `file.ts:42` - Match file.ts with line 42
|
|
105
|
-
- `file.ts:42:10` - Match with line and column
|
|
106
|
-
|
|
107
|
-
### `FileFinder.trackAccess(filePath)`
|
|
108
|
-
|
|
109
|
-
Track file access for frecency scoring.
|
|
34
|
+
Each `FileFinder` instance owns an independent native index. Create one, wait
|
|
35
|
+
for the initial scan, then run as many searches as you like.
|
|
110
36
|
|
|
111
37
|
```typescript
|
|
112
|
-
|
|
113
|
-
FileFinder.trackAccess("/path/to/file.ts");
|
|
114
|
-
```
|
|
38
|
+
import { FileFinder } from "@ff-labs/fff-bun";
|
|
115
39
|
|
|
116
|
-
|
|
40
|
+
// Create an instance bound to a directory
|
|
41
|
+
const created = FileFinder.create({ basePath: "/path/to/project" });
|
|
42
|
+
if (!created.ok) throw new Error(created.error);
|
|
117
43
|
|
|
118
|
-
|
|
44
|
+
const finder = created.value;
|
|
119
45
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
maxFileSize?: number; // Max file size in bytes (default: 10MB)
|
|
123
|
-
maxMatchesPerFile?: number; // Max matches per file (default: 200, set 0 to unlimited)
|
|
124
|
-
smartCase?: boolean; // Case-insensitive if all lowercase (default: true)
|
|
125
|
-
fileOffset?: number; // Pagination offset (default: 0)
|
|
126
|
-
pageLimit?: number; // Max matches to return (default: 50)
|
|
127
|
-
mode?: "plain" | "regex" | "fuzzy"; // Search mode (default: "plain")
|
|
128
|
-
timeBudgetMs?: number; // Time limit in ms, 0 = unlimited (default: 0)
|
|
129
|
-
}
|
|
46
|
+
// Wait for the initial scan (non-blocking)
|
|
47
|
+
await finder.waitForScan(5000);
|
|
130
48
|
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
for (const
|
|
135
|
-
console.log(
|
|
49
|
+
// 1. Fuzzy file search (typo resistant)
|
|
50
|
+
const files = finder.fileSearch("typescropt.ts", { pageSize: 10 });
|
|
51
|
+
if (files.ok) {
|
|
52
|
+
for (const item of files.value.items) {
|
|
53
|
+
console.log(item.relativePath, item.gitStatus);
|
|
136
54
|
}
|
|
137
55
|
}
|
|
138
56
|
|
|
139
|
-
//
|
|
140
|
-
const
|
|
57
|
+
// 2. Glob filter — no fuzzy matching, 100% compatible with npm `glob`
|
|
58
|
+
const globbed = finder.glob("src/**/*.ts");
|
|
59
|
+
if (globbed.ok) console.log(`${globbed.value.totalMatched} TypeScript files`);
|
|
141
60
|
|
|
142
|
-
//
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const page2 = FileFinder.grep("error", {
|
|
149
|
-
cursor: page1.value.nextCursor,
|
|
150
|
-
});
|
|
61
|
+
// 3. Content search (live grep) with pagination
|
|
62
|
+
const grep = finder.grep("TODO", { mode: "plain", pageSize: 20 });
|
|
63
|
+
if (grep.ok) {
|
|
64
|
+
for (const m of grep.value.items) {
|
|
65
|
+
console.log(`${m.relativePath}:${m.lineNumber}: ${m.lineContent}`);
|
|
66
|
+
}
|
|
151
67
|
}
|
|
152
68
|
|
|
153
|
-
//
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### `FileFinder.trackQuery(query, selectedFile)`
|
|
69
|
+
// 4. Directory search based on the query (typo resistant)
|
|
70
|
+
const dirs = finder.directorySearch("components");
|
|
71
|
+
if (dirs.ok) console.log(dirs.value.items.map((d) => d.relativePath));
|
|
159
72
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
// Call when user selects a file from search
|
|
164
|
-
FileFinder.trackQuery("main", "/path/to/main.ts");
|
|
73
|
+
// Free the resources when you don't need a file picker anymore
|
|
74
|
+
finder.destroy();
|
|
165
75
|
```
|
|
166
76
|
|
|
167
|
-
### `FileFinder.healthCheck(testPath?)`
|
|
168
|
-
|
|
169
|
-
Get diagnostic information.
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
const health = FileFinder.healthCheck();
|
|
173
|
-
if (health.ok) {
|
|
174
|
-
console.log(`Version: ${health.value.version}`);
|
|
175
|
-
console.log(`Indexed: ${health.value.filePicker.indexedFiles} files`);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
77
|
|
|
179
|
-
|
|
78
|
+
## API Reference
|
|
180
79
|
|
|
181
|
-
-
|
|
182
|
-
- `FileFinder.scanFiles()` - Trigger rescan
|
|
183
|
-
- `FileFinder.isScanning()` - Check scan status
|
|
184
|
-
- `FileFinder.getScanProgress()` - Get scan progress
|
|
185
|
-
- `FileFinder.waitForScan(timeoutMs)` - Wait for scan
|
|
186
|
-
- `FileFinder.reindex(newPath)` - Change indexed directory
|
|
187
|
-
- `FileFinder.refreshGitStatus()` - Refresh git cache
|
|
188
|
-
- `FileFinder.getHistoricalQuery(offset)` - Get past queries
|
|
189
|
-
- `FileFinder.destroy()` - Cleanup resources
|
|
80
|
+
Verify the latest API in the local interface at [`./src/fff-api.ts`](./src/fff-api.ts). Every field and type is documented.
|
|
190
81
|
|
|
191
|
-
|
|
82
|
+
### Result Types
|
|
192
83
|
|
|
193
84
|
All methods return a `Result<T>` type for explicit error handling:
|
|
194
85
|
|
|
86
|
+
|
|
195
87
|
```typescript
|
|
196
|
-
type Result<T> =
|
|
197
|
-
|
|
198
|
-
|
|
88
|
+
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
|
|
89
|
+
|
|
90
|
+
const result = finder.fileSearch("foo");
|
|
199
91
|
|
|
200
|
-
const result = FileFinder.search("foo");
|
|
201
92
|
if (result.ok) {
|
|
202
93
|
// result.value is SearchResult
|
|
203
94
|
} else {
|
|
204
|
-
// result.error is string
|
|
95
|
+
// result.error is string error message
|
|
205
96
|
}
|
|
206
97
|
```
|
|
207
98
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
interface SearchResult {
|
|
212
|
-
items: FileItem[];
|
|
213
|
-
scores: Score[];
|
|
214
|
-
totalMatched: number;
|
|
215
|
-
totalFiles: number;
|
|
216
|
-
location?: Location;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
interface FileItem {
|
|
220
|
-
path: string;
|
|
221
|
-
relativePath: string;
|
|
222
|
-
fileName: string;
|
|
223
|
-
size: number;
|
|
224
|
-
modified: number;
|
|
225
|
-
gitStatus: string; // 'clean', 'modified', 'untracked', etc.
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Grep Result Types
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
interface GrepResult {
|
|
233
|
-
items: GrepMatch[];
|
|
234
|
-
totalMatched: number;
|
|
235
|
-
totalFilesSearched: number;
|
|
236
|
-
totalFiles: number;
|
|
237
|
-
filteredFileCount: number;
|
|
238
|
-
nextCursor: GrepCursor | null; // Pass to options.cursor for next page
|
|
239
|
-
regexFallbackError?: string; // Set if regex was invalid
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
interface GrepMatch {
|
|
243
|
-
path: string;
|
|
244
|
-
relativePath: string;
|
|
245
|
-
fileName: string;
|
|
246
|
-
gitStatus: string;
|
|
247
|
-
lineNumber: number; // 1-based
|
|
248
|
-
col: number; // 0-based byte column
|
|
249
|
-
byteOffset: number; // Absolute byte offset in file
|
|
250
|
-
lineContent: string; // The matched line text
|
|
251
|
-
matchRanges: [number, number][]; // Byte offsets for highlighting
|
|
252
|
-
fuzzyScore?: number; // Only in fuzzy mode
|
|
253
|
-
}
|
|
254
|
-
```
|
|
99
|
+
This SDK calls a native compiled library for your platform at runtime. This is generally safe — fff is battle-tested and stable, and written in a memory-safe language — but there is a class of errors that can't be caught at the Bun/Node level. If you hit one, please report an issue!
|
|
255
100
|
|
|
256
101
|
## Building from Source
|
|
257
102
|
|
|
@@ -268,7 +113,7 @@ cargo build --release -p fff-c
|
|
|
268
113
|
# The binary will be at target/release/libfff_c.{so,dylib,dll}
|
|
269
114
|
```
|
|
270
115
|
|
|
271
|
-
## CLI
|
|
116
|
+
## CLI examples
|
|
272
117
|
|
|
273
118
|
```bash
|
|
274
119
|
# Download binary manually (fallback if npm package unavailable)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Glob benchmark: fff.glob vs Bun.Glob vs npm `glob`.
|
|
4
|
+
*
|
|
5
|
+
* Each engine is asked to enumerate files in a directory matching the same
|
|
6
|
+
* pattern. We measure wall-clock time + result count. fff scans + indexes
|
|
7
|
+
* once on init; the subsequent glob call is a filter over the in-memory
|
|
8
|
+
* index — that's what we time.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* bun examples/glob-bench.ts [dir] [pattern] [iterations]
|
|
12
|
+
*
|
|
13
|
+
* dir default: cwd
|
|
14
|
+
* pattern default: "**\/*.ts"
|
|
15
|
+
* iterations default: 5 (each engine runs N times, best+median reported)
|
|
16
|
+
*
|
|
17
|
+
* Install npm glob first:
|
|
18
|
+
* bun add glob
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { performance } from "node:perf_hooks";
|
|
22
|
+
import { resolve } from "node:path";
|
|
23
|
+
import { Glob as BunGlob } from "bun";
|
|
24
|
+
import { FileFinder } from "../src/index";
|
|
25
|
+
|
|
26
|
+
// npm glob — optional. Skip silently if not installed.
|
|
27
|
+
let npmGlob:
|
|
28
|
+
| ((pattern: string, opts: { cwd: string }) => Promise<string[]>)
|
|
29
|
+
| null = null;
|
|
30
|
+
try {
|
|
31
|
+
const mod: {
|
|
32
|
+
glob: (pattern: string, opts: { cwd: string }) => Promise<string[]>;
|
|
33
|
+
} =
|
|
34
|
+
// @ts-ignore - optional peer; resolved at runtime, may be absent
|
|
35
|
+
await import("glob");
|
|
36
|
+
npmGlob = mod.glob;
|
|
37
|
+
} catch {
|
|
38
|
+
console.warn("npm `glob` not installed — skipping. Run: bun add glob");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const dir = resolve(process.argv[2] ?? process.cwd());
|
|
42
|
+
const pattern = process.argv[3] ?? "**/lua/**/*.lua";
|
|
43
|
+
const iterations = Number(process.argv[4] ?? 5);
|
|
44
|
+
|
|
45
|
+
console.log(`dir: ${dir}`);
|
|
46
|
+
console.log(`pattern: ${pattern}`);
|
|
47
|
+
console.log(`iterations: ${iterations}\n`);
|
|
48
|
+
|
|
49
|
+
interface Sample {
|
|
50
|
+
ms: number;
|
|
51
|
+
count: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function summarize(label: string, samples: Sample[]): void {
|
|
55
|
+
if (samples.length === 0) {
|
|
56
|
+
console.log(`${label.padEnd(16)} skipped`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const sorted = [...samples].sort((a, b) => a.ms - b.ms);
|
|
60
|
+
const best = sorted[0]!;
|
|
61
|
+
const median = sorted[Math.floor(sorted.length / 2)]!;
|
|
62
|
+
const worst = sorted[sorted.length - 1]!;
|
|
63
|
+
const counts = new Set(samples.map((s) => s.count));
|
|
64
|
+
const countStr =
|
|
65
|
+
counts.size === 1 ? `${best.count}` : `[${[...counts].join(", ")}]`;
|
|
66
|
+
console.log(
|
|
67
|
+
`${label.padEnd(16)} best=${best.ms.toFixed(2)}ms median=${median.ms.toFixed(2)}ms worst=${worst.ms.toFixed(2)}ms count=${countStr}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function bench<T>(
|
|
72
|
+
fn: () => Promise<T> | T,
|
|
73
|
+
): Promise<{ ms: number; result: T }> {
|
|
74
|
+
const start = performance.now();
|
|
75
|
+
const result = await fn();
|
|
76
|
+
return { ms: performance.now() - start, result };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// fff: init + warm scan, then time only the .glob() call. Init cost is
|
|
81
|
+
// reported separately because it's amortized across many subsequent calls.
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
const fffInit = await bench(() => {
|
|
84
|
+
const result = FileFinder.create({
|
|
85
|
+
basePath: dir,
|
|
86
|
+
disableMmapCache: true,
|
|
87
|
+
disableContentIndexing: true,
|
|
88
|
+
disableWatch: true,
|
|
89
|
+
});
|
|
90
|
+
if (!result.ok) throw new Error(result.error);
|
|
91
|
+
return result.value;
|
|
92
|
+
});
|
|
93
|
+
const finder = fffInit.result;
|
|
94
|
+
|
|
95
|
+
// Wait until initial scan done so the first .glob() doesn't see a partial
|
|
96
|
+
// index. Returns true = completed, false = timed out.
|
|
97
|
+
const scanReady = finder.waitForScanBlocking(30_000);
|
|
98
|
+
if (!scanReady.ok || !scanReady.value) {
|
|
99
|
+
console.error("fff: initial scan did not finish in 30s — exiting");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
console.log(`fff init+scan: ${fffInit.ms.toFixed(2)}ms\n`);
|
|
103
|
+
|
|
104
|
+
const fffSamples: Sample[] = [];
|
|
105
|
+
for (let i = 0; i < iterations; i++) {
|
|
106
|
+
const r = await bench(() => {
|
|
107
|
+
const out = finder.glob(pattern, { pageSize: 100 });
|
|
108
|
+
if (!out.ok) throw new Error(out.error);
|
|
109
|
+
return out.value;
|
|
110
|
+
});
|
|
111
|
+
fffSamples.push({ ms: r.ms, count: r.result.items.length });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Bun.Glob — sync iterator, returns relative paths.
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
const bunSamples: Sample[] = [];
|
|
118
|
+
for (let i = 0; i < iterations; i++) {
|
|
119
|
+
const r = await bench(() => {
|
|
120
|
+
const g = new BunGlob(pattern);
|
|
121
|
+
let count = 0;
|
|
122
|
+
for (const _ of g.scanSync({ cwd: dir })) count++;
|
|
123
|
+
return count;
|
|
124
|
+
});
|
|
125
|
+
bunSamples.push({ ms: r.ms, count: r.result });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// npm glob — async, returns absolute or relative paths depending on opts.
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
const npmSamples: Sample[] = [];
|
|
132
|
+
if (npmGlob) {
|
|
133
|
+
for (let i = 0; i < iterations; i++) {
|
|
134
|
+
const r = await bench(() => npmGlob!(pattern, { cwd: dir }));
|
|
135
|
+
npmSamples.push({ ms: r.ms, count: r.result.length });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log("results:");
|
|
140
|
+
summarize("fff.glob", fffSamples);
|
|
141
|
+
summarize("Bun.Glob", bunSamples);
|
|
142
|
+
summarize("npm glob", npmSamples);
|
|
143
|
+
|
|
144
|
+
// Sanity: counts should be in the same ballpark. They won't match exactly
|
|
145
|
+
// because indexing rules differ (fff respects gitignore + skips binaries by
|
|
146
|
+
// default; Bun.Glob and npm glob do not).
|
|
147
|
+
const counts = {
|
|
148
|
+
fff: fffSamples[0]?.count ?? 0,
|
|
149
|
+
bun: bunSamples[0]?.count ?? 0,
|
|
150
|
+
npm: npmSamples[0]?.count ?? 0,
|
|
151
|
+
};
|
|
152
|
+
console.log(
|
|
153
|
+
`\nNote: fff respects gitignore + skips binaries; Bun.Glob and npm glob walk the raw filesystem. Count differences are expected.`,
|
|
154
|
+
);
|
|
155
|
+
console.log(
|
|
156
|
+
`raw counts: fff=${counts.fff} bun=${counts.bun} npm=${counts.npm}`,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
finder.destroy();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ff-labs/fff-bun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "High-performance fuzzy file finder for Bun - perfect for LLM agent tools",
|
|
6
6
|
"type": "module",
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
},
|
|
63
63
|
"homepage": "https://github.com/dmtrKovalenko/fff#readme",
|
|
64
64
|
"optionalDependencies": {
|
|
65
|
-
"@ff-labs/fff-bin-darwin-arm64": "0.
|
|
66
|
-
"@ff-labs/fff-bin-darwin-x64": "0.
|
|
67
|
-
"@ff-labs/fff-bin-linux-x64-gnu": "0.
|
|
68
|
-
"@ff-labs/fff-bin-linux-arm64-gnu": "0.
|
|
69
|
-
"@ff-labs/fff-bin-linux-x64-musl": "0.
|
|
70
|
-
"@ff-labs/fff-bin-linux-arm64-musl": "0.
|
|
71
|
-
"@ff-labs/fff-bin-win32-x64": "0.
|
|
72
|
-
"@ff-labs/fff-bin-win32-arm64": "0.
|
|
65
|
+
"@ff-labs/fff-bin-darwin-arm64": "0.9.0",
|
|
66
|
+
"@ff-labs/fff-bin-darwin-x64": "0.9.0",
|
|
67
|
+
"@ff-labs/fff-bin-linux-x64-gnu": "0.9.0",
|
|
68
|
+
"@ff-labs/fff-bin-linux-arm64-gnu": "0.9.0",
|
|
69
|
+
"@ff-labs/fff-bin-linux-x64-musl": "0.9.0",
|
|
70
|
+
"@ff-labs/fff-bin-linux-arm64-musl": "0.9.0",
|
|
71
|
+
"@ff-labs/fff-bin-win32-x64": "0.9.0",
|
|
72
|
+
"@ff-labs/fff-bin-win32-arm64": "0.9.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@types/bun": "^1.3.8",
|