@ff-labs/bun 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 +200 -0
- package/bin/libfff_c.dylib +0 -0
- package/package.json +74 -0
- package/scripts/cli.cjs +16 -0
- package/scripts/cli.ts +131 -0
- package/scripts/postinstall.ts +37 -0
- package/src/download.ts +316 -0
- package/src/ffi.ts +377 -0
- package/src/finder.ts +328 -0
- package/src/index.test.ts +263 -0
- package/src/index.ts +69 -0
- package/src/platform.ts +92 -0
- package/src/types.ts +260 -0
package/src/platform.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection utilities for downloading the correct binary
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the platform triple (e.g., "x86_64-unknown-linux-gnu")
|
|
9
|
+
*/
|
|
10
|
+
export function getTriple(): string {
|
|
11
|
+
const platform = process.platform;
|
|
12
|
+
const arch = process.arch;
|
|
13
|
+
|
|
14
|
+
let osName: string;
|
|
15
|
+
if (platform === "darwin") {
|
|
16
|
+
osName = "apple-darwin";
|
|
17
|
+
} else if (platform === "linux") {
|
|
18
|
+
osName = detectLinuxLibc();
|
|
19
|
+
} else if (platform === "win32") {
|
|
20
|
+
osName = "pc-windows-msvc";
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const archName = normalizeArch(arch);
|
|
26
|
+
return `${archName}-${osName}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect whether we're on musl or glibc Linux
|
|
31
|
+
*/
|
|
32
|
+
function detectLinuxLibc(): string {
|
|
33
|
+
try {
|
|
34
|
+
const lddOutput = execSync("ldd --version 2>&1", {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
timeout: 5000,
|
|
37
|
+
});
|
|
38
|
+
if (lddOutput.toLowerCase().includes("musl")) {
|
|
39
|
+
return "unknown-linux-musl";
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// ldd failed, assume glibc
|
|
43
|
+
}
|
|
44
|
+
return "unknown-linux-gnu";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize architecture name to Rust target format
|
|
49
|
+
*/
|
|
50
|
+
function normalizeArch(arch: string): string {
|
|
51
|
+
switch (arch) {
|
|
52
|
+
case "x64":
|
|
53
|
+
case "amd64":
|
|
54
|
+
return "x86_64";
|
|
55
|
+
case "arm64":
|
|
56
|
+
return "aarch64";
|
|
57
|
+
case "arm":
|
|
58
|
+
return "arm";
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the library file extension for the current platform
|
|
66
|
+
*/
|
|
67
|
+
export function getLibExtension(): "dylib" | "so" | "dll" {
|
|
68
|
+
switch (process.platform) {
|
|
69
|
+
case "darwin":
|
|
70
|
+
return "dylib";
|
|
71
|
+
case "win32":
|
|
72
|
+
return "dll";
|
|
73
|
+
default:
|
|
74
|
+
return "so";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the library filename prefix (empty on Windows)
|
|
80
|
+
*/
|
|
81
|
+
export function getLibPrefix(): string {
|
|
82
|
+
return process.platform === "win32" ? "" : "lib";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the full library filename for the current platform
|
|
87
|
+
*/
|
|
88
|
+
export function getLibFilename(): string {
|
|
89
|
+
const prefix = getLibPrefix();
|
|
90
|
+
const ext = getLibExtension();
|
|
91
|
+
return `${prefix}fff_c.${ext}`;
|
|
92
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result type for all operations - follows the Result pattern
|
|
3
|
+
*/
|
|
4
|
+
export type Result<T> =
|
|
5
|
+
| { ok: true; value: T }
|
|
6
|
+
| { ok: false; error: string };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helper to create a successful result
|
|
10
|
+
*/
|
|
11
|
+
export function ok<T>(value: T): Result<T> {
|
|
12
|
+
return { ok: true, value };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Helper to create an error result
|
|
17
|
+
*/
|
|
18
|
+
export function err<T>(error: string): Result<T> {
|
|
19
|
+
return { ok: false, error };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialization options for the file finder
|
|
24
|
+
*/
|
|
25
|
+
export interface InitOptions {
|
|
26
|
+
/** Base directory to index (required) */
|
|
27
|
+
basePath: string;
|
|
28
|
+
/** Path to frecency database (optional, defaults to ~/.fff/frecency.mdb) */
|
|
29
|
+
frecencyDbPath?: string;
|
|
30
|
+
/** Path to query history database (optional, defaults to ~/.fff/history.mdb) */
|
|
31
|
+
historyDbPath?: string;
|
|
32
|
+
/** Use unsafe no-lock mode for databases (optional, defaults to false) */
|
|
33
|
+
useUnsafeNoLock?: boolean;
|
|
34
|
+
/** Skip database initialization entirely (optional, defaults to false) */
|
|
35
|
+
skipDatabases?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Search options for fuzzy file search
|
|
40
|
+
*/
|
|
41
|
+
export interface SearchOptions {
|
|
42
|
+
/** Maximum threads for parallel search (0 = auto) */
|
|
43
|
+
maxThreads?: number;
|
|
44
|
+
/** Current file path (for deprioritization in results) */
|
|
45
|
+
currentFile?: string;
|
|
46
|
+
/** Combo boost score multiplier (default: 100) */
|
|
47
|
+
comboBoostMultiplier?: number;
|
|
48
|
+
/** Minimum combo count for boost (default: 3) */
|
|
49
|
+
minComboCount?: number;
|
|
50
|
+
/** Page index for pagination (default: 0) */
|
|
51
|
+
pageIndex?: number;
|
|
52
|
+
/** Page size for pagination (default: 100) */
|
|
53
|
+
pageSize?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A file item in search results
|
|
58
|
+
*/
|
|
59
|
+
export interface FileItem {
|
|
60
|
+
/** Absolute path to the file */
|
|
61
|
+
path: string;
|
|
62
|
+
/** Path relative to the indexed directory */
|
|
63
|
+
relativePath: string;
|
|
64
|
+
/** File name only */
|
|
65
|
+
fileName: string;
|
|
66
|
+
/** File size in bytes */
|
|
67
|
+
size: number;
|
|
68
|
+
/** Last modified timestamp (Unix seconds) */
|
|
69
|
+
modified: number;
|
|
70
|
+
/** Frecency score based on access patterns */
|
|
71
|
+
accessFrecencyScore: number;
|
|
72
|
+
/** Frecency score based on modification time */
|
|
73
|
+
modificationFrecencyScore: number;
|
|
74
|
+
/** Combined frecency score */
|
|
75
|
+
totalFrecencyScore: number;
|
|
76
|
+
/** Git status: 'clean', 'modified', 'untracked', 'staged_new', etc. */
|
|
77
|
+
gitStatus: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Score breakdown for a search result
|
|
82
|
+
*/
|
|
83
|
+
export interface Score {
|
|
84
|
+
/** Total combined score */
|
|
85
|
+
total: number;
|
|
86
|
+
/** Base fuzzy match score */
|
|
87
|
+
baseScore: number;
|
|
88
|
+
/** Bonus for filename match */
|
|
89
|
+
filenameBonus: number;
|
|
90
|
+
/** Bonus for special filenames (index.ts, main.rs, etc.) */
|
|
91
|
+
specialFilenameBonus: number;
|
|
92
|
+
/** Boost from frecency */
|
|
93
|
+
frecencyBoost: number;
|
|
94
|
+
/** Penalty for distance in path */
|
|
95
|
+
distancePenalty: number;
|
|
96
|
+
/** Penalty if this is the current file */
|
|
97
|
+
currentFilePenalty: number;
|
|
98
|
+
/** Boost from query history combo matching */
|
|
99
|
+
comboMatchBoost: number;
|
|
100
|
+
/** Whether this was an exact match */
|
|
101
|
+
exactMatch: boolean;
|
|
102
|
+
/** Type of match: 'fuzzy', 'exact', 'prefix', etc. */
|
|
103
|
+
matchType: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Location in file (from query like "file.ts:42")
|
|
108
|
+
*/
|
|
109
|
+
export type Location =
|
|
110
|
+
| { type: "line"; line: number }
|
|
111
|
+
| { type: "position"; line: number; col: number }
|
|
112
|
+
| {
|
|
113
|
+
type: "range";
|
|
114
|
+
start: { line: number; col: number };
|
|
115
|
+
end: { line: number; col: number };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Search result from fuzzy file search
|
|
120
|
+
*/
|
|
121
|
+
export interface SearchResult {
|
|
122
|
+
/** Matched file items */
|
|
123
|
+
items: FileItem[];
|
|
124
|
+
/** Corresponding scores for each item */
|
|
125
|
+
scores: Score[];
|
|
126
|
+
/** Total number of files that matched */
|
|
127
|
+
totalMatched: number;
|
|
128
|
+
/** Total number of indexed files */
|
|
129
|
+
totalFiles: number;
|
|
130
|
+
/** Location parsed from query (e.g., "file.ts:42:10") */
|
|
131
|
+
location?: Location;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Scan progress information
|
|
136
|
+
*/
|
|
137
|
+
export interface ScanProgress {
|
|
138
|
+
/** Number of files scanned so far */
|
|
139
|
+
scannedFilesCount: number;
|
|
140
|
+
/** Whether a scan is currently in progress */
|
|
141
|
+
isScanning: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Database health information
|
|
146
|
+
*/
|
|
147
|
+
export interface DbHealth {
|
|
148
|
+
/** Path to the database */
|
|
149
|
+
path: string;
|
|
150
|
+
/** Size of the database on disk in bytes */
|
|
151
|
+
diskSize: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Health check result
|
|
156
|
+
*/
|
|
157
|
+
export interface HealthCheck {
|
|
158
|
+
/** Library version */
|
|
159
|
+
version: string;
|
|
160
|
+
/** Git integration status */
|
|
161
|
+
git: {
|
|
162
|
+
/** Whether git2 library is available */
|
|
163
|
+
available: boolean;
|
|
164
|
+
/** Whether a git repository was found */
|
|
165
|
+
repositoryFound: boolean;
|
|
166
|
+
/** Git working directory path */
|
|
167
|
+
workdir?: string;
|
|
168
|
+
/** libgit2 version string */
|
|
169
|
+
libgit2Version: string;
|
|
170
|
+
/** Error message if git detection failed */
|
|
171
|
+
error?: string;
|
|
172
|
+
};
|
|
173
|
+
/** File picker status */
|
|
174
|
+
filePicker: {
|
|
175
|
+
/** Whether the file picker is initialized */
|
|
176
|
+
initialized: boolean;
|
|
177
|
+
/** Base path being indexed */
|
|
178
|
+
basePath?: string;
|
|
179
|
+
/** Whether a scan is in progress */
|
|
180
|
+
isScanning?: boolean;
|
|
181
|
+
/** Number of indexed files */
|
|
182
|
+
indexedFiles?: number;
|
|
183
|
+
/** Error message if there's an issue */
|
|
184
|
+
error?: string;
|
|
185
|
+
};
|
|
186
|
+
/** Frecency database status */
|
|
187
|
+
frecency: {
|
|
188
|
+
/** Whether frecency tracking is initialized */
|
|
189
|
+
initialized: boolean;
|
|
190
|
+
/** Database health information */
|
|
191
|
+
dbHealthcheck?: DbHealth;
|
|
192
|
+
/** Error message if there's an issue */
|
|
193
|
+
error?: string;
|
|
194
|
+
};
|
|
195
|
+
/** Query tracker status */
|
|
196
|
+
queryTracker: {
|
|
197
|
+
/** Whether query tracking is initialized */
|
|
198
|
+
initialized: boolean;
|
|
199
|
+
/** Database health information */
|
|
200
|
+
dbHealthcheck?: DbHealth;
|
|
201
|
+
/** Error message if there's an issue */
|
|
202
|
+
error?: string;
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Internal: Options format sent to Rust FFI
|
|
208
|
+
* @internal
|
|
209
|
+
*/
|
|
210
|
+
export interface InitOptionsInternal {
|
|
211
|
+
base_path: string;
|
|
212
|
+
frecency_db_path?: string;
|
|
213
|
+
history_db_path?: string;
|
|
214
|
+
use_unsafe_no_lock: boolean;
|
|
215
|
+
skip_databases: boolean;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Internal: Search options format sent to Rust FFI
|
|
220
|
+
* @internal
|
|
221
|
+
*/
|
|
222
|
+
export interface SearchOptionsInternal {
|
|
223
|
+
max_threads?: number;
|
|
224
|
+
current_file?: string;
|
|
225
|
+
combo_boost_multiplier?: number;
|
|
226
|
+
min_combo_count?: number;
|
|
227
|
+
page_index?: number;
|
|
228
|
+
page_size?: number;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Convert public InitOptions to internal format
|
|
233
|
+
* @internal
|
|
234
|
+
*/
|
|
235
|
+
export function toInternalInitOptions(opts: InitOptions): InitOptionsInternal {
|
|
236
|
+
return {
|
|
237
|
+
base_path: opts.basePath,
|
|
238
|
+
frecency_db_path: opts.frecencyDbPath,
|
|
239
|
+
history_db_path: opts.historyDbPath,
|
|
240
|
+
use_unsafe_no_lock: opts.useUnsafeNoLock ?? false,
|
|
241
|
+
skip_databases: opts.skipDatabases ?? false,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Convert public SearchOptions to internal format
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
export function toInternalSearchOptions(
|
|
250
|
+
opts?: SearchOptions
|
|
251
|
+
): SearchOptionsInternal {
|
|
252
|
+
return {
|
|
253
|
+
max_threads: opts?.maxThreads,
|
|
254
|
+
current_file: opts?.currentFile,
|
|
255
|
+
combo_boost_multiplier: opts?.comboBoostMultiplier,
|
|
256
|
+
min_combo_count: opts?.minComboCount,
|
|
257
|
+
page_index: opts?.pageIndex,
|
|
258
|
+
page_size: opts?.pageSize,
|
|
259
|
+
};
|
|
260
|
+
}
|